Solvespace

Форк
0
/
importidf.cpp 
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
13
static std::vector <std::string> splitString(const std::string line) {
14
    std::vector <std::string> v = {};
15

16
    if(line.length() == 0) return v;
17

18
    std::string s = "";
19
    bool inString = false;
20
    bool inQuotes = false;
21
    
22
    for (size_t i=0; i<line.length(); i++) {
23
        char c = line.at(i);
24
        if (inQuotes) {
25
            if (c != '"') {
26
                s.push_back(c);
27
            } else {
28
                v.push_back(s);
29
                inQuotes = false;
30
                inString = false;
31
                s = "";
32
            }
33
        } else if (inString) {
34
            if (c != ' ') {
35
                s.push_back(c);
36
            } else {
37
                v.push_back(s);
38
                inString = false;
39
                s = "";
40
            }
41
        } else if(c == '"') {
42
            inString = true;
43
            inQuotes = true;
44
        } else if(c != ' ') {
45
            s = "";
46
            s.push_back(c);
47
            inString = true;
48
        }
49
    }
50
    if(s.length() > 0)
51
        v.push_back(s);
52

53
    return v;
54
}
55

56
static bool isHoleDuplicate(EntityList *el, double x, double y, double r) {
57
    bool duplicate = false;
58
    for(int i = 0; i < el->n && !duplicate; i++) {
59
        Entity &en = el->Get(i);
60
        if(en.type != Entity::Type::CIRCLE)
61
            continue;
62
        Entity *distance = el->FindById(en.distance);
63
        Entity *center   = el->FindById(en.point[0]);
64
        duplicate =
65
            center->actPoint.x == x && center->actPoint.y == y && distance->actDistance == r;
66
    }
67
    return 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
76
static hEntity newPoint(EntityList *el, int *id, Vector p, bool visible = true) {
77
    Entity en = {};
78
    en.type = Entity::Type::POINT_N_COPY;
79
    en.extraPoints = 0;
80
    en.timesApplied = 0;
81
    en.group.v = 462;
82
    en.actPoint = p;
83
    en.construction = false;
84
    en.style.v = Style::DATUM;
85
    en.actVisible = visible;
86
    en.forceHidden = false;
87

88
    *id = *id+1;
89
    en.h.v = *id + en.group.v*65536;    
90
    el->Add(&en);
91
    return en.h;
92
}
93

94
static hEntity newLine(EntityList *el, int *id, hEntity p0, hEntity p1, bool keepout) {
95
    Entity en = {};
96
    en.type = Entity::Type::LINE_SEGMENT;
97
    en.point[0] = p0;
98
    en.point[1] = p1;
99
    en.extraPoints = 0;
100
    en.timesApplied = 0;
101
    en.group.v = 493;
102
    en.construction = keepout;
103
    en.style.v = keepout? Style::CONSTRUCTION : Style::ACTIVE_GRP;
104
    en.actVisible = true;
105
    en.forceHidden = false;
106

107
    *id = *id+1;
108
    en.h.v = *id + en.group.v*65536;    
109
    el->Add(&en);
110
    return en.h;
111
}
112

113
static 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 this
115
    Entity en = {};
116
    en.type = Entity::Type::NORMAL_N_COPY;
117
    en.extraPoints = 0;
118
    en.timesApplied = 0;
119
    en.group.v = 472;
120
    en.actNormal = normal;
121
    en.construction = false;
122
    en.style.v = Style::ACTIVE_GRP;
123
    // to be visible we need to add a point.
124
    en.point[0] = newPoint(el, id, Vector::From(0,0,3), /*visible=*/ true);
125
    en.actVisible = true;
126
    en.forceHidden = false;
127

128
    *id = *id+1;
129
    en.h.v = *id + en.group.v*65536;    
130
    el->Add(&en);
131
    return en.h;
132
}
133

134
static hEntity newArc(EntityList *el, int *id, hEntity p0, hEntity p1, hEntity pc, hEntity hnorm, bool keepout) {
135
    Entity en = {};
136
    en.type = Entity::Type::ARC_OF_CIRCLE;
137
    en.point[0] = pc;
138
    en.point[1] = p0;
139
    en.point[2] = p1;
140
    en.normal = hnorm;
141
    en.extraPoints = 0;
142
    en.timesApplied = 0;
143
    en.group.v = 403;
144
    en.construction = keepout;
145
    en.style.v = keepout? Style::CONSTRUCTION : Style::ACTIVE_GRP;
146
    en.actVisible = true;
147
    en.forceHidden = false;    *id = *id+1;
148

149
    *id = *id + 1;
150
    en.h.v = *id + en.group.v*65536;
151
    el->Add(&en);
152
    return en.h;
153
}
154

155
static 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 this
157
    Entity en = {};
158
    en.type = Entity::Type::DISTANCE;
159
    en.extraPoints = 0;
160
    en.timesApplied = 0;
161
    en.group.v = 472;
162
    en.actDistance = distance;
163
    en.construction = false;
164
    en.style.v = Style::ACTIVE_GRP;
165
    // to be visible we'll need to add a point?
166
    en.actVisible = false;
167
    en.forceHidden = false;
168

169
    *id = *id+1;
170
    en.h.v = *id + en.group.v*65536;    
171
    el->Add(&en);
172
    return en.h;
173
}
174

175
static hEntity newCircle(EntityList *el, int *id, hEntity p0, hEntity hdist, hEntity hnorm, bool keepout) {
176
    Entity en = {};
177
    en.type = Entity::Type::CIRCLE;
178
    en.point[0] = p0;
179
    en.normal = hnorm;
180
    en.distance = hdist;
181
    en.extraPoints = 0;
182
    en.timesApplied = 0;
183
    en.group.v = 399;
184
    en.construction = keepout;
185
    en.style.v = keepout? Style::CONSTRUCTION : Style::ACTIVE_GRP;
186
    en.actVisible = true;
187
    en.forceHidden = false;
188

189
    *id = *id+1;
190
    en.h.v = *id + en.group.v*65536;
191
    el->Add(&en);
192
    return en.h;
193
}
194

195
static Vector ArcCenter(Vector p0, Vector p1, double angle) {
196
    // locate the center of an arc
197
    Vector m = p0.Plus(p1).ScaledBy(0.5);
198
    Vector perp = Vector::From(p1.y-p0.y, p0.x-p1.x, 0.0).WithMagnitude(1.0);
199
    double dist = 0;
200
    if (angle != 180) {
201
        dist = (p1.Minus(m).Magnitude())/tan(0.5*angle*3.141592653589793/180.0);
202
    } else {
203
        dist = 0.0;
204
    }
205
    Vector c = m.Minus(perp.ScaledBy(dist));
206
    return 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.
212
static void CreateEntity(EntityList *el, int *id, hEntity h0, hEntity h1, hEntity hnorm,
213
                    Vector p0, Vector p1, double angle, bool keepout) {
214
    if (angle == 0.0) {
215
        //line
216
        if(p0.Equals(p1)) return;
217

218
        newLine(el, id, h0, h1, keepout);
219

220
    } else if(angle == 360.0) {
221
        // circle
222
        double d = p1.Minus(p0).Magnitude();
223
        hEntity hd = newDistance(el, id, d);
224
        newCircle(el, id, h1, hd, hnorm, keepout);
225
        
226
    } else {
227
        // arc
228
        if(angle < 0.0) {
229
            swap(p0,p1);
230
            swap(h0,h1);
231
        }
232
        // locate the center of the arc
233
        Vector m = p0.Plus(p1).ScaledBy(0.5);
234
        Vector perp = Vector::From(p1.y-p0.y, p0.x-p1.x, 0.0).WithMagnitude(1.0);
235
        double dist = 0;
236
        if (angle != 180) {
237
            dist = (p1.Minus(m).Magnitude())/tan(0.5*angle*3.141592653589793/180.0);
238
        } else {
239
            dist = 0.0;
240
        }
241
        Vector c = m.Minus(perp.ScaledBy(dist));
242
        hEntity hc = newPoint(el, id, c, /*visible=*/false);
243
        newArc(el, id, h0, h1, hc, hnorm, keepout);
244
    }
245
}
246

247
// borrowed from Entity::GenerateBezierCurves because we don't have parameters.
248
static void MakeBeziersForArcs(SBezierList *sbl, Vector center, Vector pa, Vector pb,
249
                               Quaternion q, double angle) {
250

251
    Vector u = q.RotationU(), v = q.RotationV();
252
    double r = pa.Minus(center).Magnitude();
253
    double theta, dtheta;
254
    
255
    if(angle == 360.0) {
256
        theta = 0;
257
    } else {
258
        Point2d c2  = center.Project2d(u, v);
259
        Point2d pa2 = (pa.Project2d(u, v)).Minus(c2);
260

261
        theta = atan2(pa2.y, pa2.x);
262
    }
263
    dtheta = angle * PI/180;
264
    
265
    int i, n;
266
    if(dtheta > (3*PI/2 + 0.01)) {
267
        n = 4;
268
    } else if(dtheta > (PI + 0.01)) {
269
        n = 3;
270
    } else if(dtheta > (PI/2 + 0.01)) {
271
        n = 2;
272
    } else {
273
        n = 1;
274
    }
275
    dtheta /= n;
276

277
    for(i = 0; i < n; i++) {
278
        double s, c;
279

280
        c = cos(theta);
281
        s = sin(theta);
282
        // The start point of the curve, and the tangent vector at
283
        // that start point.
284
        Vector p0 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)),
285
               t0 =             u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c));
286

287
        theta += dtheta;
288

289
        c = cos(theta);
290
        s = sin(theta);
291
        Vector p2 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)),
292
               t2 =             u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c));
293

294
        // The control point must lie on both tangents.
295
        Vector p1 = Vector::AtIntersectionOfLines(p0, p0.Plus(t0),
296
                                                  p2, p2.Plus(t2),
297
                                                  NULL);
298

299
        SBezier sb = SBezier::From(p0, p1, p2);
300
        sb.weight[1] = cos(dtheta/2);
301
        sbl->l.Add(&sb);
302
    }
303
}
304

305
namespace 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.
313
bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *sh) {
314
    dbp("\nLink IDF board outline.");
315
    el->Clear();
316
    std::string data;
317
    if(!ReadFile(filename, &data)) {
318
        Error("Couldn't read from '%s'", filename.raw.c_str());
319
        return false;
320
    }
321
    
322
    enum IDF_SECTION {
323
        none,
324
        header,
325
        board_outline,
326
        other_outline,
327
        routing_outline,
328
        placement_outline,
329
        routing_keepout,
330
        via_keepout,
331
        placement_group,
332
        drilled_holes,
333
        notes,
334
        component_placement
335
    } section;
336

337
    section = IDF_SECTION::none;
338
    int record_number = 0;
339
    int curve = -1;
340
    int entityCount = 0;
341
    
342
    hEntity hprev;
343
    hEntity hprevTop;
344
    Vector pprev = Vector::From(0,0,0);
345
    Vector pprevTop = Vector::From(0,0,0);
346
    
347
    double board_thickness = 10.0;
348
    double scale = 1.0; //mm
349
    bool topEntities       = false;
350
    bool bottomEntities    = false;
351

352
    Quaternion normal = Quaternion::From(Vector::From(1,0,0), Vector::From(0,1,0));
353
    hEntity hnorm = newNormal(el, &entityCount, normal);
354

355
    // to create the extursion we will need to collect a set of bezier curves defined
356
    // by the perimeter, cutouts, and holes.
357
    SBezierList sbl = {};
358
    
359
    std::stringstream stream(data);
360
    for(std::string line; getline( stream, line ); ) {
361
        if (line.find(".END_") == 0) {
362
            section = none;
363
            curve = -1;
364
        }
365
        switch (section) {
366
            case none:
367
                if(line.find(".HEADER") == 0) {
368
                    section = header;
369
                    record_number = 1;
370
                } else if (line.find(".BOARD_OUTLINE") == 0) {
371
                    section = board_outline;
372
                    record_number = 1;
373
                } else if (line.find(".ROUTE_KEEPOUT") == 0) {
374
                    section = routing_keepout;
375
                    record_number = 1;
376
                } else if(line.find(".DRILLED_HOLES") == 0) {
377
                    section = drilled_holes;
378
                    record_number = 1;
379
                }
380
                break;
381
                
382
            case header:
383
                if(record_number == 3) {
384
                    if(line.find("MM") != std::string::npos) {
385
                        dbp("IDF units are MM");
386
                        scale = 1.0;
387
                    } else if(line.find("THOU") != std::string::npos) {
388
                        dbp("IDF units are thousandths of an inch");
389
                        scale = 0.0254;
390
                    } else {
391
                        dbp("IDF import, no units found in file.");
392
                    }
393
                }
394
                break;
395
            
396
            case routing_keepout:   
397
            case board_outline:
398
                if (record_number == 2) {
399
                    if(section == board_outline) {
400
                        topEntities = true;
401
                        bottomEntities = true;
402
                        board_thickness = std::stod(line) * scale;
403
                        dbp("IDF board thickness: %lf", board_thickness);
404
                    } else if (section == routing_keepout) {
405
                        topEntities = false;
406
                        bottomEntities = false;
407
                        if(line.find("TOP") == 0 || line.find("BOTH") == 0)
408
                            topEntities = true;
409
                        if(line.find("BOTTOM") == 0 || line.find("BOTH") == 0)
410
                            bottomEntities = true;
411
                    }
412
                } else { // records 3+ are lines, arcs, and circles
413
                    std::vector <std::string> values = splitString(line);
414
                    if(values.size() != 4) continue;
415
                    int c = stoi(values[0]);
416
                    double x = stof(values[1]);
417
                    double y = stof(values[2]);
418
                    double ang = stof(values[3]);
419
                    Vector point = Vector::From(x,y,0.0);
420
                    Vector pTop = Vector::From(x,y,board_thickness);
421
                    if(c != curve) { // start a new curve
422
                        curve = c;
423
                        if (bottomEntities)
424
                            hprev = newPoint(el, &entityCount, point, /*visible=*/false);
425
                        if (topEntities)
426
                            hprevTop = newPoint(el, &entityCount, pTop, /*visible=*/false);
427
                        pprev = point;
428
                        pprevTop = pTop;
429
                    } else {
430
                        if(section == board_outline) {
431
                            // create a bezier for the extrusion
432
                            if (ang == 0) {
433
                                // straight lines
434
                                SBezier sb = SBezier::From(pprev, point);
435
                                sbl.l.Add(&sb);
436
                            } else if (ang != 360.0) {
437
                                // Arcs
438
                                Vector c = ArcCenter(pprev, point, ang);
439
                                MakeBeziersForArcs(&sbl, c, pprev, point, normal, ang);
440
                            } else {
441
                                // circles
442
                                MakeBeziersForArcs(&sbl, point, pprev, pprev, normal, ang);
443
                            }
444
                        }
445
                        // next create the entities
446
                        // only curves and points at circle centers will be visible
447
                        bool vis = (ang == 360.0);
448
                        if (bottomEntities) {
449
                            hEntity hp = newPoint(el, &entityCount, point, /*visible=*/vis);
450
                            CreateEntity(el, &entityCount, hprev, hp, hnorm, pprev, point, ang,
451
                                (section == routing_keepout) );
452
                            pprev = point;
453
                            hprev = hp;
454
                        }
455
                        if (topEntities) {
456
                            hEntity hp = newPoint(el, &entityCount, pTop, /*visible=*/vis);
457
                            CreateEntity(el, &entityCount, hprevTop, hp, hnorm, pprevTop, pTop,
458
                                 ang, (section == routing_keepout) );
459
                            pprevTop = pTop;
460
                            hprevTop = hp;
461
                        }
462
                    }
463
                }
464
                break;
465

466
            case other_outline:
467
            case routing_outline:
468
            case placement_outline:
469
            case via_keepout:
470
            case placement_group:
471
                break;
472

473
            case drilled_holes: {
474
                    std::vector <std::string> values = splitString(line);
475
                    if(values.size() < 6) continue;
476
                    double d = stof(values[0]);
477
                    double x = stof(values[1]);
478
                    double y = stof(values[2]);
479
                    bool duplicate = isHoleDuplicate(el, x, y, d / 2);
480
                    // Only show holes likely to be useful in MCAD to reduce complexity.
481
                    if(((d > 1.7) || (values[5].compare(0,3,"PIN") == 0)
482
                         || (values[5].compare(0,3,"MTG") == 0)) && !duplicate) {
483
                        // create the entity
484
                        Vector cent = Vector::From(x,y,0.0);
485
                        hEntity hcent = newPoint(el, &entityCount, cent);
486
                        hEntity hdist = newDistance(el, &entityCount, d/2);
487
                        newCircle(el, &entityCount, hcent, hdist, hnorm, false);
488
                        // and again for the top
489
                        Vector cTop = Vector::From(x,y,board_thickness);
490
                        hcent = newPoint(el, &entityCount, cTop);
491
                        hdist = newDistance(el, &entityCount, d/2);
492
                        newCircle(el, &entityCount, hcent, hdist, hnorm, false);
493
                        // create the curves for the extrusion
494
                        Vector pt = Vector::From(x+d/2, y, 0.0);
495
                        MakeBeziersForArcs(&sbl, cent, pt, pt, normal, 360.0);
496
                    }
497

498
                    break;
499
                }            
500
            case notes:
501
            case component_placement:
502
                break;
503
                
504
            default:
505
                section = none;
506
                break;
507
        }
508
        record_number++;
509
    }
510
    // now we can create an extrusion from all the Bezier curves. We can skip things
511
    // like checking for a coplanar sketch because everything is at z=0.
512
    SPolygon polyLoops = {};
513
    bool allClosed;
514
    bool allCoplanar;
515
    Vector errorPointAt = Vector::From(0,0,0);
516
    SEdge errorAt = {};
517
    
518
    SBezierLoopSetSet sblss = {};
519
    sblss.FindOuterFacesFrom(&sbl, &polyLoops, NULL,
520
                             100.0, &allClosed, &errorAt,
521
                             &allCoplanar, &errorPointAt, NULL);
522

523
    //hack for when there is no sketch yet and the first group is a linked IDF
524
    double ctc = SS.chordTolCalculated;
525
    if(ctc == 0.0) SS.chordTolCalculated = 0.1; //mm
526
    // there should only by one sbls in the sblss unless a board has disjointed parts...
527
    sh->MakeFromExtrusionOf(sblss.l.First(), Vector::From(0.0, 0.0, 0.0),
528
                                   Vector::From(0.0, 0.0, board_thickness),
529
                                   RgbaColor::From(0, 180, 0) );
530
    SS.chordTolCalculated = ctc;
531
    sblss.Clear();
532
    sbl.Clear();
533
    sh->booleanFailed = false;
534
    
535
    return true;
536
}
537

538
}
539

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.