/* * hyp2mat - convert hyperlynx files to matlab scripts * Copyright 2012 Koen De Vleeschauwer. * * 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 #include #include #include #include "csxcad.h" using namespace Hyp2Mat; CSXCAD::CSXCAD() { /* CSXcad priorities */ prio_dielectric = 100; // FR4 dielectric prio_material = 200; // copper prio_via = 300; // via metal prio_drill = 400; // hole return; } void CSXCAD::export_edge(FloatPolygon& edge) { std::cout << "pgon = [];" << std::endl; for (FloatPolygon::iterator i = edge.begin(); i != edge.end(); ++i) { std::cout << "pgon(:, end+1) = [" << i->x << ";" << i->y << "];" << std::endl; } return; } /* * quote a string using matlab conventions. */ std::string CSXCAD::string2matlab(std::string str) { std::ostringstream ostring; // escape non-alpha characters, or WriteOpenEMS (in matlab) may crash on characters such as '%' in strings ostring << "'"; for (unsigned int i = 0; i < str.size(); i++) { if (str[i] == '\'') ostring << '\''; if (str[i] == '%') ostring << '%'; ostring << str[i]; } ostring << "'"; return ostring.str(); } /* true if polygon list contains at least one (positive) polygon */ bool CSXCAD::contains_polygon(Hyp2Mat::FloatPolygons& polygons) { bool result = false; for (FloatPolygons::iterator i = polygons.begin(); i != polygons.end(); ++i) { result = !i->is_hole; if (result) break; }; return result; } /* true if polygon list contains at least one hole */ bool CSXCAD::contains_hole(Hyp2Mat::FloatPolygons& polygons) { bool result = false; for (FloatPolygons::iterator i = polygons.begin(); i != polygons.end(); ++i) { result = i->is_hole; if (result) break; }; return result; } /* * Export dielectric * If pcb_outline is true, export exact board shape, including holes. * If pcb_outline is false, export bounding box. */ void CSXCAD::export_board(Hyp2Mat::PCB& pcb, bool pcb_outline) { /* CSXCAD coordinate grid definition */ Bounds bounds = pcb.GetBounds(); std::cout << "function CSX = pcb(CSX)" << std::endl; std::cout << "% matlab script created by hyp2mat" << std::endl; std::cout << "% create minimal mesh" << std::endl; std::cout << "mesh = {};" << std::endl; std::cout << "mesh.x = [" << bounds.x_min << " " << bounds.x_max << "];" << std::endl; std::cout << "mesh.y = [" << bounds.y_min << " " << bounds.y_max << "];" << std::endl; std::cout << "mesh.z = [" << bounds.z_min << " " << bounds.z_max << "];" << std::endl; std::cout << "% add mesh" << std::endl; std::cout << "CSX = DefineRectGrid(CSX, 1, mesh);" << std::endl; /* * Export the board. The board outline is positive; */ /* * create board material if at least one positive polygon present * We output one polygon for each dielectric layer, as each layer may have * a different epsilon_r. */ if (contains_polygon(pcb.board)) { for (FloatPolygons::iterator i = pcb.board.begin(); i != pcb.board.end(); ++i) { /* only output board material, not holes */ if (i->is_hole) continue; for (LayerList::reverse_iterator l = pcb.stackup.rbegin(); l != pcb.stackup.rend(); ++l) { /* only output dielectrics */ if (l->layer_type != LAYER_DIELECTRIC) continue; /* Output command to create material */ std::cout << "CSX = AddHyperLynxDielectric(CSX, 'Dielectric_" << l->layer_name << "', " << l->epsilon_r << ", " << l->loss_tangent << ");" << std::endl; /* output CSXCAD polygon */ std::cout << "% board outline, layer " << l->layer_name << std::endl; int priority = prio_dielectric + i->nesting_level; if (pcb_outline) { /* Use AddLinPoly */ export_edge(i->poly); std::cout << "CSX = AddLinPoly(CSX, 'Dielectric_" << l->layer_name << "', " << priority << ", 2, " << l->z0 << ", pgon, " << l->z1 - l->z0 << ");" << std::endl; } else { /* Use AddBox */ std::cout << "CSX = AddBox(CSX, 'Dielectric_" << l->layer_name << "', " << priority << ", [ "; std::cout << bounds.x_min << ", " << bounds.y_min << ", " << l->z0 << "], [ "; std::cout << bounds.x_max << ", " << bounds.y_max << ", " << l->z1 << "] );" << std::endl; } } } }; /* * Export board cutouts */ /* * create board cutout material if at least one negative polygon present * For each cutout we create a single polygon which goes through all layers. */ if (pcb_outline && contains_hole(pcb.board)) { std::cout << "% create board cutout material" << std::endl; std::cout << "CSX = AddMaterial( CSX, 'Drill');" << std::endl; std::cout << "CSX = SetMaterialProperty( CSX, 'Drill', 'Epsilon', 1, 'Mue', 1);" << std::endl; for (FloatPolygons::iterator i = pcb.board.begin(); i != pcb.board.end(); ++i) { /* output CSXCAD polygon */ if (!i->is_hole) continue; std::cout << "% board cutout" << std::endl; export_edge(i->poly); int priority = prio_dielectric + i->nesting_level; std::cout << "CSX = AddLinPoly(CSX, 'Drill', " << priority << ", 2, " << bounds.z_min << ", pgon, " << bounds.z_max - bounds.z_min << ");" << std::endl; } }; return; } /* * Export copper. * If lossy_copper is true, take copper conductivity into account. * If lossy_copper is false, assume copper is perfect conductor. */ void CSXCAD::export_layer(Hyp2Mat::PCB& pcb, Hyp2Mat::Layer& layer, bool lossy_copper, bool metal_3d, bool odd) { std::string layer_material = layer.layer_name + "_copper"; std::string layer_cutout = layer.layer_name + "_cutout"; // create layer material if at least one positive polygon present if (contains_polygon(layer.metal)) { std::cout << "% create layer " << layer.layer_name << " material" << std::endl; double copper_resistivity = 0.0; if (lossy_copper && (layer.bulk_resistivity > 0)) copper_resistivity = layer.bulk_resistivity; if (metal_3d) std::cout << "CSX = AddHyperLynxMetal3D(CSX, '" << layer_material << "', " << copper_resistivity << ", " << layer.thickness << ");" << std::endl; else std::cout << "CSX = AddHyperLynxMetal2D(CSX, '" << layer_material << "', " << copper_resistivity << ", " << layer.thickness << ");" << std::endl; }; // create layer cutout material if at least one hole present if (contains_hole(layer.metal)) { std::cout << "% create layer " << layer.layer_name << " cutout" << std::endl; std::cout << "CSX = AddMaterial( CSX, '" << layer_cutout << "');" << std::endl; // outer and inner copper layers have different epsilonr std::cout << "CSX = SetMaterialProperty( CSX, '" << layer_cutout << "', 'Epsilon', " << layer.epsilon_r << ", 'Mue', 1);" << std::endl; }; /* * Export the layer. */ std::string material; for (FloatPolygons::iterator i = layer.metal.begin(); i != layer.metal.end(); ++i) { /* output CSXCAD polygon */ if (!i->is_hole) { std::cout << "% copper" << std::endl; material = layer_material; } else { std::cout << "% cutout" << std::endl; material = layer_cutout; }; export_edge(i->poly); int priority = prio_material + i->nesting_level; /* Default is exporting copper as a 2D conducting sheet */ if (metal_3d) { // We assume alternating layers of substrate and prepreg,and that metal layers are pushed into the prepreg. // This means that the top metal layer is pointing up, the second metal layer is pointing down, and so on. double thickness = layer.thickness; if (odd) thickness = -thickness; std::cout << "CSX = AddLinPoly(CSX, '" << material << "', " << priority << ", 2, " << layer.z0 << ", pgon, " << thickness << ");" << std::endl; } else std::cout << "CSX = AddPolygon(CSX, '" << material << "', " << priority << ", 2, " << layer.z0 << ", pgon);" << std::endl; } return; } /* * Export vias */ void CSXCAD::export_vias(Hyp2Mat::PCB& pcb) { if (!pcb.via.empty()) { /* create via material */ std::cout << "% via copper" << std::endl; std::cout << "CSX = AddMetal( CSX, 'via' );" << std::endl; for (ViaList::iterator i = pcb.via.begin(); i != pcb.via.end(); ++i) { double z0 = i->z0; double z1 = i->z1; std::cout << "CSX = AddCylinder(CSX, 'via', " << prio_via ; std::cout << ", [ " << i->x << " , " << i->y << " , " << z0; std::cout << " ], [ " << i->x << " , " << i->y << " , " << z1; std::cout << " ], " << i->radius << ");" << std::endl; } } return; } /* * Export devices */ void CSXCAD::export_devices(Hyp2Mat::PCB& pcb) { std::cout << "% devices" << std::endl; std::cout << "CSX.HyperLynxDevice = {};" << std::endl; for (DeviceList::iterator i = pcb.device.begin(); i != pcb.device.end(); ++i) { std::cout << "CSX.HyperLynxDevice{end+1} = struct('name', " << string2matlab(i->name) << ", 'ref', " << string2matlab(i->ref); /* output device value if available */ if (i->value_type == DEVICE_VALUE_FLOAT) std::cout << ", 'value', " << i->value_float; else if (i->value_type == DEVICE_VALUE_STRING) std::cout << ", 'value', " << string2matlab(i->value_string); std::cout << ", 'layer_name', " << string2matlab(i->layer_name); std::cout << ");" << std::endl; } } /* * Export port */ void CSXCAD::export_ports(Hyp2Mat::PCB& pcb) { std::cout << "% ports" << std::endl; std::cout << "CSX.HyperLynxPort = {};" << std::endl; for (PinList::iterator i = pcb.pin.begin(); i != pcb.pin.end(); ++i) { /* csxcad requires rectangular ports, axis aligned. Use the bounding box. XXX fixme ? Use largest inscribed rectangle instead */ 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); bool on_top = dtop <= dbottom; std::cout << "CSX.HyperLynxPort{end+1} = struct('ref', " << string2matlab(i->ref); std::cout << ", 'xc', " << i->x << ", 'yc', " << i->y << ", 'z', " << i->z0; std::cout << ", 'x1', " << x_min << ", 'y1', " << y_min ; std::cout << ", 'x2', " << x_max << ", 'y2', " << y_max ; std::cout << ", 'position', " << (on_top ? "'top'" : "'bottom'"); std::cout << ", 'layer_name', " << string2matlab(i->layer_name) << ");" << std::endl; } return; } /* * Write pcb to file in CSXCAD format */ void CSXCAD::Write(const std::string& filename, Hyp2Mat::PCB pcb, bool pcb_outline, bool lossy_copper, bool metal_3d) { /* open file for output */ if ((filename != "-") && (freopen(filename.c_str(), "w", stdout) == NULL)) { std::cerr << "could not open '" << filename << "' for writing"; return; } /* Export dielectric. If pcb_outline is true, export exact board shape, including holes. If pcb_outline is false, export bounding box. */ export_board(pcb, pcb_outline); /* Export copper */ std::cout << "% copper" << std::endl; bool odd = false; for (LayerList::iterator l = pcb.stackup.begin(); l != pcb.stackup.end(); ++l) { if (l->layer_type == LAYER_DIELECTRIC) odd = !odd; export_layer(pcb, *l, lossy_copper, metal_3d, odd); } /* Export vias */ export_vias(pcb); export_devices(pcb); export_ports(pcb); std::cout << "%not truncated" << std::endl; fclose(stdout); return; } /* not truncated */