From f18d63e5950dd9a28a8465e1b840026cc4274193 Mon Sep 17 00:00:00 2001 From: Koen De Vleeschauwer Date: Thu, 16 Jul 2020 10:37:55 +0200 Subject: [PATCH] add QucsStudio pcb format --- THANKS | 2 + configure.ac | 2 +- lib/Makefile.am | 2 + lib/copper.cc | 10 +- lib/hyp2mat.h | 6 ++ lib/pcb.cc | 11 ++ lib/qucsstudio.cc | 254 ++++++++++++++++++++++++++++++++++++++++++++++ lib/qucsstudio.h | 41 ++++++++ src/cmdline.ggo | 4 +- src/hyp2mat.cc | 4 + 10 files changed, 328 insertions(+), 8 deletions(-) create mode 100644 lib/qucsstudio.cc create mode 100644 lib/qucsstudio.h diff --git a/THANKS b/THANKS index b1df414..3602292 100644 --- a/THANKS +++ b/THANKS @@ -5,3 +5,5 @@ Thorsten Liebig and Sebastian Held from OpenEMS. Ulf Niessmann for suggesting ClipperLib. Todd Elliott. + +Michael Margraf for QucsStudio pcb format https://dd6um.darc.de/QucsStudio/ diff --git a/configure.ac b/configure.ac index c441dbc..c3801f9 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ # Process this file with autoconf to produce a configure script. -AC_INIT([hyp2mat], [0.0.17]) +AC_INIT([hyp2mat], [0.0.18]) AC_CONFIG_AUX_DIR([config]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_SRCDIR([src/hyp2mat.cc]) diff --git a/lib/Makefile.am b/lib/Makefile.am index 5e1000d..e2d0523 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -52,6 +52,8 @@ libhyp2mat_la_SOURCES=\ pcb.cc \ pdf.cc \ pdf.h \ + qucsstudio.h \ + qucsstudio.cc \ scan.ll EXTRA_DIST=clipper.txt parse.h diff --git a/lib/copper.cc b/lib/copper.cc index 3b5addd..7678eab 100644 --- a/lib/copper.cc +++ b/lib/copper.cc @@ -149,13 +149,13 @@ Hyp2Mat::Polygon HyperLynx::CopyCopper(Hyp2Mat::Layer layer, Hyp2Mat::FloatPolyg The second time we calculate plane polygon pours. Plane polygons have a clearance from copper of other nets */ /* Determine which nets we need to include */ - for (int i = 0; i < hyp_file.net.size(); ++i) { + for (unsigned int i = 0; i < hyp_file.net.size(); ++i) { /* check if we're interested in this net. if no nets are specified, copy all nets */ net_wanted[i] = nets.empty() || (std::find(nets.begin(), nets.end(), hyp_file.net[i].net_name) != nets.end()); } /* calculate all copper. */ - for (int i = 0; i < hyp_file.net.size(); ++i) { + for (unsigned int i = 0; i < hyp_file.net.size(); ++i) { /* skip unwanted nets */ if (!net_wanted[i]) continue; @@ -175,7 +175,7 @@ Hyp2Mat::Polygon HyperLynx::CopyCopper(Hyp2Mat::Layer layer, Hyp2Mat::FloatPolyg Hyp2Mat::Polygon layer_copper; /* add plane and pour polygons */ - for (int i = 0; i < hyp_file.net.size(); ++i) { + for (unsigned int i = 0; i < hyp_file.net.size(); ++i) { /* skip unwanted nets */ if (!net_wanted[i]) continue; @@ -185,7 +185,7 @@ Hyp2Mat::Polygon HyperLynx::CopyCopper(Hyp2Mat::Layer layer, Hyp2Mat::FloatPolyg } /* subtract antipads from pour and plane polygons. */ - for (int i = 0; i < hyp_file.net.size(); ++i) { + for (unsigned int i = 0; i < hyp_file.net.size(); ++i) { /* skip unwanted nets */ if (!net_wanted[i]) continue; @@ -194,7 +194,7 @@ Hyp2Mat::Polygon HyperLynx::CopyCopper(Hyp2Mat::Layer layer, Hyp2Mat::FloatPolyg } /* add copper and pad polygons */ - for (int i = 0; i < hyp_file.net.size(); ++i) { + for (unsigned int i = 0; i < hyp_file.net.size(); ++i) { /* skip unwanted nets */ if (!net_wanted[i]) continue; diff --git a/lib/hyp2mat.h b/lib/hyp2mat.h index 94a095e..a28988b 100644 --- a/lib/hyp2mat.h +++ b/lib/hyp2mat.h @@ -200,6 +200,12 @@ namespace Hyp2Mat { void WriteCSXCAD(std::string filename, bool pcb_outline = false, bool lossy_copper = false, bool metal_3d = false); + /* + * WriteQucsStudio exports QucsStudio PCB format to file "filename". + */ + + bool WriteQucsStudio(std::string filename); + unsigned int debug; /* setting debug to 0 switches debugging off */ std::vector flood_layers; /* names of layers to be flooded with copper */ bool raw; /* set raw processing */ diff --git a/lib/pcb.cc b/lib/pcb.cc index 86bc657..3a4c700 100644 --- a/lib/pcb.cc +++ b/lib/pcb.cc @@ -27,6 +27,7 @@ #include "hyperlynx.h" #include "pdf.h" #include "csxcad.h" +#include "qucsstudio.h" using namespace Hyp2Mat; @@ -101,6 +102,16 @@ void PCB::WriteCSXCAD (std::string filename, bool pcb_outline, bool lossy_copper csxcad.Write(filename, *this, pcb_outline, lossy_copper, metal_3d); } +/* + * Write a pcb in QucsStudio format + */ + +bool PCB::WriteQucsStudio (std::string filename) +{ + QucsStudio qs; + return qs.Write(filename, *this); +} + /* * Set dielectric epsilon r. Overrides value in Hyperlynx file. */ diff --git a/lib/qucsstudio.cc b/lib/qucsstudio.cc new file mode 100644 index 0000000..8c8aff8 --- /dev/null +++ b/lib/qucsstudio.cc @@ -0,0 +1,254 @@ +/* + * hyp2mat - convert hyperlynx files to QucsStudio PCB format + * Copyright 2018 Michael Margraf. + * + * This file is part of hyp2mat. + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see . + */ + +#include +#include + +#include "qucsstudio.h" + +// define the version of output file +#define PACKAGE_VERSION "3.3.1" + +// define the layout file identifier +#define QUCSLAYOUT_ID "thickness; + Bounds bounds = pcb.GetBounds(); + double dx = 0.5*(bounds.x_max - bounds.x_min); + double dy = 0.5*(bounds.y_max - bounds.y_min); + + std::cout << "" << std::endl + << "DataSet=*.dat" << std::endl + << "DataDisplay=*.dpl" << std::endl + << "OpenDisplay=1" << std::endl + << "Grid=0.1 mm" << std::endl + << "Mesh=20" << std::endl + << "Excite=2|0.0001|0" << std::endl + << "Frequency=1 GHz|9 GHz|401" << std::endl + << "Boundary=" << dx << "|" << dy << "|2" << std::endl; + + bool no_substrate_found = true; + + for (FloatPolygons::iterator i = pcb.board.begin(); i != pcb.board.end(); ++i) { + if (i->is_hole) + continue; // don't output holes + + for (LayerList::reverse_iterator l = pcb.stackup.rbegin(); l != pcb.stackup.rend(); ++l) { + if (l->layer_type != LAYER_DIELECTRIC) + continue; // only output dielectrics + + if(no_substrate_found) { + no_substrate_found = false; + + // output material parameters + std::cout << "Substrate=" << l->layer_name + << "|" << l->epsilon_r + << "|" << l->z1 - l->z0 + << "|" << l->loss_tangent + << "|0|" << thickness << std::endl; + } + else + std::cerr << "Warning: More than one substrate found. Ignoring it." << std::endl; + } + } + + if(no_substrate_found) + std::cout << "Substrate=Subst1|1.0|1 mm|0|0|0" << std::endl; + + std::cout << "" << std::endl; +} + +// ------------------------------------------------------------------ +// export the metallization layer +void QucsStudio::export_layer(Hyp2Mat::PCB& pcb, Hyp2Mat::Layer& layer) +{ + int n; + char buf[32]; + double cx, cy, dx, dy; + FloatPolygon::iterator j; + FloatPolygons::iterator i; + Hyp2Mat::FloatPolygon edge; + + n = 0; + for (i = layer.metal.begin(); i != layer.metal.end(); ++i) { + if (i->is_hole) + continue; + + sprintf(buf, "metal%d ", n++); + edge = i->poly; + j = edge.begin(); + cx = j->x; + cy = j->y; + + if (edge.size() == 4) { // check if it's a rectangle + if (edge.at(0).x == edge.at(1).x) { + if (edge.at(1).y == edge.at(2).y) + if (edge.at(2).x == edge.at(3).x) + if (edge.at(3).y == edge.at(0).y) { + dx = edge.at(2).x - edge.at(1).x; + if (dx < 0.0) { + dx = -dx; + cx = edge.at(2).x; + } + dy = edge.at(1).y - edge.at(0).y; + if (dy < 0.0) { + dy = -dy; + cy = edge.at(1).y; + } + std::cout << "Rectangle " << buf << cx << " " << cy + << " 0|0|" << dx << "|" << dy << std::endl; + continue; + } + } + else if (edge.at(0).y == edge.at(1).y) { + if (edge.at(1).x == edge.at(2).x) + if (edge.at(2).y == edge.at(3).y) + if (edge.at(3).x == edge.at(0).x) { + dx = edge.at(1).x - edge.at(0).x; + if (dx < 0.0) { + dx = -dx; + cx = edge.at(1).x; + } + dy = edge.at(2).y - edge.at(1).y; + if (dy < 0.0) { + dy = -dy; + cy = edge.at(2).y; + } + std::cout << "Rectangle " << buf << cx << " " << cy + << " 0|0|" << dx << "|" << dy << std::endl; + continue; + } + } + } + + std::cout << "Polygon " << buf << cx << " " << cy << " "; + for (; j != edge.end(); ++j) + std::cout << "|" << (j->x - cx) << "|" << (j->y - cy); + std::cout << std::endl; + } +} + +// ------------------------------------------------------------------ +void QucsStudio::export_vias(Hyp2Mat::PCB& pcb) +{ + for (ViaList::iterator i = pcb.via.begin(); i != pcb.via.end(); ++i) + std::cout << "Via X " << i->x << " " << i->y << " " + << i->radius << std::endl; +} + +// ------------------------------------------------------------------ +void QucsStudio::export_devices(Hyp2Mat::PCB& pcb) +{ + for (DeviceList::iterator i = pcb.device.begin(); i != pcb.device.end(); ++i) { + std::cerr << "Device: name=" << i->name << ", ref=" << i->ref; + + // output device value if available + if (i->value_type == DEVICE_VALUE_FLOAT) + std::cerr << ", value=" << i->value_float; + else if (i->value_type == DEVICE_VALUE_STRING) + std::cerr << ", value=" << i->value_string; + + std::cerr << ", layer_name=" << i->layer_name; + std::cerr << std::endl; + } +} + +// ------------------------------------------------------------------ +// PCB ports are the connection point of wire lines. So usually every wire +// owns a port, but these are not exciting sources for an EM simulation. +void QucsStudio::export_ports(Hyp2Mat::PCB& pcb) +{ + int no = 0; + for (PinList::iterator i = pcb.pin.begin(); i != pcb.pin.end(); ++i) { + no++; + + #if 0 + double x_max = 0, y_max = 0, x_min = 0, y_min = 0; + bool first = true; + for (FloatPolygon::iterator j = i->metal.begin(); j != i->metal.end(); ++j) { + if ((j->x > x_max) || first) x_max = j->x; + if ((j->y > y_max) || first) y_max = j->y; + if ((j->x < x_min) || first) x_min = j->x; + if ((j->y < y_min) || first) y_min = j->y; + first = false; + } + + // determine whether port is on top or bottom layer of pcb + double dbottom = std::abs(i->z0 - pcb.stackup.back().z0); + double dtop = std::abs(i->z1 - pcb.stackup.front().z1); + if (dtop > dbottom) // port on bottom? + continue; + + x_max -= x_min; + y_max -= y_min; + if (x_max >= y_max) + y_max = 0.0; + else + x_max = 0.0; + + std::cout << "Lumped " << i->ref << " " << i->x << " " << i->y << " " + << no << " 2 50 0 " << x_max << " " << y_max << std::endl; + #else + std::cerr << "Port: ref=" << i->ref << ", x=" << i->x << ", y=" << i->y << std::endl; + #endif + } +} + +// ------------------------------------------------------------------ +// write PCB to file in QucsStudio format +bool QucsStudio::Write(const std::string& filename, Hyp2Mat::PCB pcb) +{ + if ((filename != "-") && (freopen(filename.c_str(), "w", stdout) == NULL)) { + std::cerr << "ERROR: Cannot open '" << filename << "' for writing."; + return false; + } + + std::cout << QUCSLAYOUT_ID PACKAGE_VERSION ">" << std::endl; + + export_board(pcb); + + // export metalization + std::cout << "" << std::endl; + export_layer(pcb, *pcb.stackup.begin()); + if (pcb.stackup.size() > 1) + std::cerr << "Warning: More than one layer found. Ignoring it." << std::endl; + + export_vias(pcb); + + export_devices(pcb); + + export_ports(pcb); + + std::cout << "" << std::endl; + fclose(stdout); + return true; +} + +/* not truncated */ diff --git a/lib/qucsstudio.h b/lib/qucsstudio.h new file mode 100644 index 0000000..ce4780d --- /dev/null +++ b/lib/qucsstudio.h @@ -0,0 +1,41 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2018 Michael Margraf. + * + * This file is part of hyp2mat. + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see . + */ + +#ifndef QUCSSTUDIO_H +#define QUCSSTUDIO_H + +#include "hyp2mat.h" + +class QucsStudio { +public: + QucsStudio(); + bool Write(const std::string& filename, Hyp2Mat::PCB pcb); /* save in QucsStudio format */ + +private: + void export_layer(Hyp2Mat::PCB& pcb, Hyp2Mat::Layer& layer); /* output a copper layer */ + void export_board(Hyp2Mat::PCB& pcb); /* output the dielectric */ + void export_vias(Hyp2Mat::PCB& pcb); + void export_devices(Hyp2Mat::PCB& pcb); + void export_ports(Hyp2Mat::PCB& pcb); +}; + +#endif + +/* not truncated */ diff --git a/src/cmdline.ggo b/src/cmdline.ggo index 85f4c18..9f0bcce 100644 --- a/src/cmdline.ggo +++ b/src/cmdline.ggo @@ -3,12 +3,12 @@ # args "--unamed-opts" -usage " [-h] [ -o outfile ] [ -f pdf|csxcad ] [-n net]... [OPTIONS]... [-v] [infile]" +usage " [-h] [ -o outfile ] [ -f pdf|csxcad|qucs ] [-n net]... [OPTIONS]... [-v] [infile]" description "Converts Hyperlynx Signal-Integrity Transfer Format files to Octave/matlab scripts." option "output" o "Output file." string typestr="filename" default="-" optional -option "output-format" f "Output file format." enum values="csxcad", "pdf" default="pdf" optional +option "output-format" f "Output file format." enum values="csxcad", "pdf", "qucs" default="pdf" optional section "Processing options" option "net" n "Import net. Repeat to import several nets. Default is importing all nets." string optional multiple option "layer" l "Import layer. Repeat to import several layers. Default is importing all layers." string optional multiple diff --git a/src/hyp2mat.cc b/src/hyp2mat.cc index 7c40161..b77fe70 100644 --- a/src/hyp2mat.cc +++ b/src/hyp2mat.cc @@ -143,6 +143,10 @@ int main(int argc, char **argv) case output_format_arg_csxcad: pcb.WriteCSXCAD(output_file, args_info.pcb_outline_flag, args_info.lossy_copper_flag, args_info.metal_3d_flag); break; + /* Export QucsStudio PCB format */ + case output_format_arg_qucs: + pcb.WriteQucsStudio(output_file); + break; default: std::cerr << "unknown output format " << args_info.output_format_arg << std::endl; break;