Solvespace
538 строк · 19.1 Кб
1//-----------------------------------------------------------------------------
2// Intermediate Data Format (IDF) file reader. Reads an IDF file for PCB outlines and creates
3// an equivalent SovleSpace sketch/extrusion. Supports only Linking, not import.
4// Part placement is not currently supported.
5//
6// Copyright 2020 Paul Kahler.
7//-----------------------------------------------------------------------------
8#include "solvespace.h"9#include "sketch.h"10
11// Split a string into substrings separated by spaces.
12// Allow quotes to enclose spaces within a string
13static std::vector <std::string> splitString(const std::string line) {14std::vector <std::string> v = {};15
16if(line.length() == 0) return v;17
18std::string s = "";19bool inString = false;20bool inQuotes = false;21
22for (size_t i=0; i<line.length(); i++) {23char c = line.at(i);24if (inQuotes) {25if (c != '"') {26s.push_back(c);27} else {28v.push_back(s);29inQuotes = false;30inString = false;31s = "";32}33} else if (inString) {34if (c != ' ') {35s.push_back(c);36} else {37v.push_back(s);38inString = false;39s = "";40}41} else if(c == '"') {42inString = true;43inQuotes = true;44} else if(c != ' ') {45s = "";46s.push_back(c);47inString = true;48}49}50if(s.length() > 0)51v.push_back(s);52
53return v;54}
55
56static bool isHoleDuplicate(EntityList *el, double x, double y, double r) {57bool duplicate = false;58for(int i = 0; i < el->n && !duplicate; i++) {59Entity &en = el->Get(i);60if(en.type != Entity::Type::CIRCLE)61continue;62Entity *distance = el->FindById(en.distance);63Entity *center = el->FindById(en.point[0]);64duplicate =65center->actPoint.x == x && center->actPoint.y == y && distance->actDistance == r;66}67return duplicate;68}
69
70//////////////////////////////////////////////////////////////////////////////
71// Functions for linking an IDF file - we need to create entities that
72// get remapped into a linked group similar to linking .slvs files
73//////////////////////////////////////////////////////////////////////////////
74
75// Make a new point - type doesn't matter since we will make a copy later
76static hEntity newPoint(EntityList *el, int *id, Vector p, bool visible = true) {77Entity en = {};78en.type = Entity::Type::POINT_N_COPY;79en.extraPoints = 0;80en.timesApplied = 0;81en.group.v = 462;82en.actPoint = p;83en.construction = false;84en.style.v = Style::DATUM;85en.actVisible = visible;86en.forceHidden = false;87
88*id = *id+1;89en.h.v = *id + en.group.v*65536;90el->Add(&en);91return en.h;92}
93
94static hEntity newLine(EntityList *el, int *id, hEntity p0, hEntity p1, bool keepout) {95Entity en = {};96en.type = Entity::Type::LINE_SEGMENT;97en.point[0] = p0;98en.point[1] = p1;99en.extraPoints = 0;100en.timesApplied = 0;101en.group.v = 493;102en.construction = keepout;103en.style.v = keepout? Style::CONSTRUCTION : Style::ACTIVE_GRP;104en.actVisible = true;105en.forceHidden = false;106
107*id = *id+1;108en.h.v = *id + en.group.v*65536;109el->Add(&en);110return en.h;111}
112
113static hEntity newNormal(EntityList *el, int *id, Quaternion normal) {114// normals have parameters, but we don't need them to make a NORMAL_N_COPY from this115Entity en = {};116en.type = Entity::Type::NORMAL_N_COPY;117en.extraPoints = 0;118en.timesApplied = 0;119en.group.v = 472;120en.actNormal = normal;121en.construction = false;122en.style.v = Style::ACTIVE_GRP;123// to be visible we need to add a point.124en.point[0] = newPoint(el, id, Vector::From(0,0,3), /*visible=*/ true);125en.actVisible = true;126en.forceHidden = false;127
128*id = *id+1;129en.h.v = *id + en.group.v*65536;130el->Add(&en);131return en.h;132}
133
134static hEntity newArc(EntityList *el, int *id, hEntity p0, hEntity p1, hEntity pc, hEntity hnorm, bool keepout) {135Entity en = {};136en.type = Entity::Type::ARC_OF_CIRCLE;137en.point[0] = pc;138en.point[1] = p0;139en.point[2] = p1;140en.normal = hnorm;141en.extraPoints = 0;142en.timesApplied = 0;143en.group.v = 403;144en.construction = keepout;145en.style.v = keepout? Style::CONSTRUCTION : Style::ACTIVE_GRP;146en.actVisible = true;147en.forceHidden = false; *id = *id+1;148
149*id = *id + 1;150en.h.v = *id + en.group.v*65536;151el->Add(&en);152return en.h;153}
154
155static hEntity newDistance(EntityList *el, int *id, double distance) {156// normals have parameters, but we don't need them to make a NORMAL_N_COPY from this157Entity en = {};158en.type = Entity::Type::DISTANCE;159en.extraPoints = 0;160en.timesApplied = 0;161en.group.v = 472;162en.actDistance = distance;163en.construction = false;164en.style.v = Style::ACTIVE_GRP;165// to be visible we'll need to add a point?166en.actVisible = false;167en.forceHidden = false;168
169*id = *id+1;170en.h.v = *id + en.group.v*65536;171el->Add(&en);172return en.h;173}
174
175static hEntity newCircle(EntityList *el, int *id, hEntity p0, hEntity hdist, hEntity hnorm, bool keepout) {176Entity en = {};177en.type = Entity::Type::CIRCLE;178en.point[0] = p0;179en.normal = hnorm;180en.distance = hdist;181en.extraPoints = 0;182en.timesApplied = 0;183en.group.v = 399;184en.construction = keepout;185en.style.v = keepout? Style::CONSTRUCTION : Style::ACTIVE_GRP;186en.actVisible = true;187en.forceHidden = false;188
189*id = *id+1;190en.h.v = *id + en.group.v*65536;191el->Add(&en);192return en.h;193}
194
195static Vector ArcCenter(Vector p0, Vector p1, double angle) {196// locate the center of an arc197Vector m = p0.Plus(p1).ScaledBy(0.5);198Vector perp = Vector::From(p1.y-p0.y, p0.x-p1.x, 0.0).WithMagnitude(1.0);199double dist = 0;200if (angle != 180) {201dist = (p1.Minus(m).Magnitude())/tan(0.5*angle*3.141592653589793/180.0);202} else {203dist = 0.0;204}205Vector c = m.Minus(perp.ScaledBy(dist));206return c;207}
208
209// Add an IDF line or arc to the entity list. According to spec, zero angle indicates a line.
210// Positive angles are counter clockwise, negative are clockwise. An angle of 360
211// indicates a circle centered at x1,y1 passing through x2,y2 and is a complete loop.
212static void CreateEntity(EntityList *el, int *id, hEntity h0, hEntity h1, hEntity hnorm,213Vector p0, Vector p1, double angle, bool keepout) {214if (angle == 0.0) {215//line216if(p0.Equals(p1)) return;217
218newLine(el, id, h0, h1, keepout);219
220} else if(angle == 360.0) {221// circle222double d = p1.Minus(p0).Magnitude();223hEntity hd = newDistance(el, id, d);224newCircle(el, id, h1, hd, hnorm, keepout);225
226} else {227// arc228if(angle < 0.0) {229swap(p0,p1);230swap(h0,h1);231}232// locate the center of the arc233Vector m = p0.Plus(p1).ScaledBy(0.5);234Vector perp = Vector::From(p1.y-p0.y, p0.x-p1.x, 0.0).WithMagnitude(1.0);235double dist = 0;236if (angle != 180) {237dist = (p1.Minus(m).Magnitude())/tan(0.5*angle*3.141592653589793/180.0);238} else {239dist = 0.0;240}241Vector c = m.Minus(perp.ScaledBy(dist));242hEntity hc = newPoint(el, id, c, /*visible=*/false);243newArc(el, id, h0, h1, hc, hnorm, keepout);244}245}
246
247// borrowed from Entity::GenerateBezierCurves because we don't have parameters.
248static void MakeBeziersForArcs(SBezierList *sbl, Vector center, Vector pa, Vector pb,249Quaternion q, double angle) {250
251Vector u = q.RotationU(), v = q.RotationV();252double r = pa.Minus(center).Magnitude();253double theta, dtheta;254
255if(angle == 360.0) {256theta = 0;257} else {258Point2d c2 = center.Project2d(u, v);259Point2d pa2 = (pa.Project2d(u, v)).Minus(c2);260
261theta = atan2(pa2.y, pa2.x);262}263dtheta = angle * PI/180;264
265int i, n;266if(dtheta > (3*PI/2 + 0.01)) {267n = 4;268} else if(dtheta > (PI + 0.01)) {269n = 3;270} else if(dtheta > (PI/2 + 0.01)) {271n = 2;272} else {273n = 1;274}275dtheta /= n;276
277for(i = 0; i < n; i++) {278double s, c;279
280c = cos(theta);281s = sin(theta);282// The start point of the curve, and the tangent vector at283// that start point.284Vector p0 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)),285t0 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c));286
287theta += dtheta;288
289c = cos(theta);290s = sin(theta);291Vector p2 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)),292t2 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c));293
294// The control point must lie on both tangents.295Vector p1 = Vector::AtIntersectionOfLines(p0, p0.Plus(t0),296p2, p2.Plus(t2),297NULL);298
299SBezier sb = SBezier::From(p0, p1, p2);300sb.weight[1] = cos(dtheta/2);301sbl->l.Add(&sb);302}303}
304
305namespace SolveSpace {306
307// Here we read the important section of an IDF file. SolveSpace Entities are directly created by
308// the functions above, which is only OK because of the way linking works. For example points do
309// not have handles for solver parameters (coordinates), they only have their actPoint values
310// set (or actNormal or actDistance). These are incomplete entities and would be a problem if
311// they were part of the sketch, but they are not. After making a list of them here, a new group
312// gets created from copies of these. Those copies are complete and part of the sketch group.
313bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *sh) {314dbp("\nLink IDF board outline.");315el->Clear();316std::string data;317if(!ReadFile(filename, &data)) {318Error("Couldn't read from '%s'", filename.raw.c_str());319return false;320}321
322enum IDF_SECTION {323none,324header,325board_outline,326other_outline,327routing_outline,328placement_outline,329routing_keepout,330via_keepout,331placement_group,332drilled_holes,333notes,334component_placement
335} section;336
337section = IDF_SECTION::none;338int record_number = 0;339int curve = -1;340int entityCount = 0;341
342hEntity hprev;343hEntity hprevTop;344Vector pprev = Vector::From(0,0,0);345Vector pprevTop = Vector::From(0,0,0);346
347double board_thickness = 10.0;348double scale = 1.0; //mm349bool topEntities = false;350bool bottomEntities = false;351
352Quaternion normal = Quaternion::From(Vector::From(1,0,0), Vector::From(0,1,0));353hEntity hnorm = newNormal(el, &entityCount, normal);354
355// to create the extursion we will need to collect a set of bezier curves defined356// by the perimeter, cutouts, and holes.357SBezierList sbl = {};358
359std::stringstream stream(data);360for(std::string line; getline( stream, line ); ) {361if (line.find(".END_") == 0) {362section = none;363curve = -1;364}365switch (section) {366case none:367if(line.find(".HEADER") == 0) {368section = header;369record_number = 1;370} else if (line.find(".BOARD_OUTLINE") == 0) {371section = board_outline;372record_number = 1;373} else if (line.find(".ROUTE_KEEPOUT") == 0) {374section = routing_keepout;375record_number = 1;376} else if(line.find(".DRILLED_HOLES") == 0) {377section = drilled_holes;378record_number = 1;379}380break;381
382case header:383if(record_number == 3) {384if(line.find("MM") != std::string::npos) {385dbp("IDF units are MM");386scale = 1.0;387} else if(line.find("THOU") != std::string::npos) {388dbp("IDF units are thousandths of an inch");389scale = 0.0254;390} else {391dbp("IDF import, no units found in file.");392}393}394break;395
396case routing_keepout:397case board_outline:398if (record_number == 2) {399if(section == board_outline) {400topEntities = true;401bottomEntities = true;402board_thickness = std::stod(line) * scale;403dbp("IDF board thickness: %lf", board_thickness);404} else if (section == routing_keepout) {405topEntities = false;406bottomEntities = false;407if(line.find("TOP") == 0 || line.find("BOTH") == 0)408topEntities = true;409if(line.find("BOTTOM") == 0 || line.find("BOTH") == 0)410bottomEntities = true;411}412} else { // records 3+ are lines, arcs, and circles413std::vector <std::string> values = splitString(line);414if(values.size() != 4) continue;415int c = stoi(values[0]);416double x = stof(values[1]);417double y = stof(values[2]);418double ang = stof(values[3]);419Vector point = Vector::From(x,y,0.0);420Vector pTop = Vector::From(x,y,board_thickness);421if(c != curve) { // start a new curve422curve = c;423if (bottomEntities)424hprev = newPoint(el, &entityCount, point, /*visible=*/false);425if (topEntities)426hprevTop = newPoint(el, &entityCount, pTop, /*visible=*/false);427pprev = point;428pprevTop = pTop;429} else {430if(section == board_outline) {431// create a bezier for the extrusion432if (ang == 0) {433// straight lines434SBezier sb = SBezier::From(pprev, point);435sbl.l.Add(&sb);436} else if (ang != 360.0) {437// Arcs438Vector c = ArcCenter(pprev, point, ang);439MakeBeziersForArcs(&sbl, c, pprev, point, normal, ang);440} else {441// circles442MakeBeziersForArcs(&sbl, point, pprev, pprev, normal, ang);443}444}445// next create the entities446// only curves and points at circle centers will be visible447bool vis = (ang == 360.0);448if (bottomEntities) {449hEntity hp = newPoint(el, &entityCount, point, /*visible=*/vis);450CreateEntity(el, &entityCount, hprev, hp, hnorm, pprev, point, ang,451(section == routing_keepout) );452pprev = point;453hprev = hp;454}455if (topEntities) {456hEntity hp = newPoint(el, &entityCount, pTop, /*visible=*/vis);457CreateEntity(el, &entityCount, hprevTop, hp, hnorm, pprevTop, pTop,458ang, (section == routing_keepout) );459pprevTop = pTop;460hprevTop = hp;461}462}463}464break;465
466case other_outline:467case routing_outline:468case placement_outline:469case via_keepout:470case placement_group:471break;472
473case drilled_holes: {474std::vector <std::string> values = splitString(line);475if(values.size() < 6) continue;476double d = stof(values[0]);477double x = stof(values[1]);478double y = stof(values[2]);479bool duplicate = isHoleDuplicate(el, x, y, d / 2);480// Only show holes likely to be useful in MCAD to reduce complexity.481if(((d > 1.7) || (values[5].compare(0,3,"PIN") == 0)482|| (values[5].compare(0,3,"MTG") == 0)) && !duplicate) {483// create the entity484Vector cent = Vector::From(x,y,0.0);485hEntity hcent = newPoint(el, &entityCount, cent);486hEntity hdist = newDistance(el, &entityCount, d/2);487newCircle(el, &entityCount, hcent, hdist, hnorm, false);488// and again for the top489Vector cTop = Vector::From(x,y,board_thickness);490hcent = newPoint(el, &entityCount, cTop);491hdist = newDistance(el, &entityCount, d/2);492newCircle(el, &entityCount, hcent, hdist, hnorm, false);493// create the curves for the extrusion494Vector pt = Vector::From(x+d/2, y, 0.0);495MakeBeziersForArcs(&sbl, cent, pt, pt, normal, 360.0);496}497
498break;499}500case notes:501case component_placement:502break;503
504default:505section = none;506break;507}508record_number++;509}510// now we can create an extrusion from all the Bezier curves. We can skip things511// like checking for a coplanar sketch because everything is at z=0.512SPolygon polyLoops = {};513bool allClosed;514bool allCoplanar;515Vector errorPointAt = Vector::From(0,0,0);516SEdge errorAt = {};517
518SBezierLoopSetSet sblss = {};519sblss.FindOuterFacesFrom(&sbl, &polyLoops, NULL,520100.0, &allClosed, &errorAt,521&allCoplanar, &errorPointAt, NULL);522
523//hack for when there is no sketch yet and the first group is a linked IDF524double ctc = SS.chordTolCalculated;525if(ctc == 0.0) SS.chordTolCalculated = 0.1; //mm526// there should only by one sbls in the sblss unless a board has disjointed parts...527sh->MakeFromExtrusionOf(sblss.l.First(), Vector::From(0.0, 0.0, 0.0),528Vector::From(0.0, 0.0, board_thickness),529RgbaColor::From(0, 180, 0) );530SS.chordTolCalculated = ctc;531sblss.Clear();532sbl.Clear();533sh->booleanFailed = false;534
535return true;536}
537
538}
539