Embedded libraries for all custom components

Added DPS internal libraries to file structure to improve portability
This commit is contained in:
DragonflyPS 2024-01-10 10:53:05 -08:00
parent be9fa5484e
commit 72872b8c9e
194 changed files with 228838 additions and 80285 deletions

View File

@ -1,7 +1,7 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('STEP AP214'),'1');
FILE_NAME('CR_LHM3H_VIS','2024-01-10T04:05:19',(''),(''),'','','');
FILE_NAME('CR_M384A_VIS','2023-11-05T09:11:12',(''),(''),'','','');
FILE_SCHEMA(('AUTOMOTIVE_DESIGN'));
ENDSEC;
DATA;
@ -229,13 +229,13 @@ DATA;
#230=DIRECTION('',(0.,0.,1.));
#231=DIRECTION('',(0.,1.,0.));
#232=CARTESIAN_POINT('',(0.,0.,0.));
#233=CARTESIAN_POINT('',(-1.443,0.0,0.813));
#234=CARTESIAN_POINT('',(-1.533,0.0,0.815));
#235=CARTESIAN_POINT('',(-1.443,0.0,0.815));
#236=CARTESIAN_POINT('',(-1.533,0.0,0.813));
#237=CARTESIAN_POINT('',(-1.443,0.0,0.813));
#238=CARTESIAN_POINT('',(-1.443,0.0,0.813));
#239=CARTESIAN_POINT('',(-1.443,0.0,0.815));
#233=CARTESIAN_POINT('',(-1.443,0.0,1.143));
#234=CARTESIAN_POINT('',(-1.533,0.0,1.146));
#235=CARTESIAN_POINT('',(-1.443,0.0,1.146));
#236=CARTESIAN_POINT('',(-1.533,0.0,1.143));
#237=CARTESIAN_POINT('',(-1.443,0.0,1.143));
#238=CARTESIAN_POINT('',(-1.443,0.0,1.143));
#239=CARTESIAN_POINT('',(-1.443,0.0,1.146));
#240=STYLED_ITEM('color',(#52),#190);
#241=STYLED_ITEM('color',(#52),#191);
#242=STYLED_ITEM('color',(#52),#192);
@ -245,37 +245,37 @@ DATA;
#247=ADVANCED_FACE('',(#253),#248,.T.);
#248=PLANE('',#249);
#249=AXIS2_PLACEMENT_3D('',#250,#251,#252);
#250=CARTESIAN_POINT('',(-1.704,-0.993,0.813));
#250=CARTESIAN_POINT('',(-1.648,-0.937,1.143));
#251=DIRECTION('',(0.0,0.0,1.0));
#252=DIRECTION('',(0.,1.,0.));
#253=FACE_OUTER_BOUND('',#254,.T.);
#254=EDGE_LOOP('',(#255,#265,#275,#285));
#258=CARTESIAN_POINT('',(1.704,-0.993,0.813));
#258=CARTESIAN_POINT('',(1.648,-0.937,1.143));
#257=VERTEX_POINT('',#258);
#260=CARTESIAN_POINT('',(-1.704,-0.993,0.813));
#260=CARTESIAN_POINT('',(-1.648,-0.937,1.143));
#259=VERTEX_POINT('',#260);
#256=EDGE_CURVE('',#257,#259,#261,.T.);
#261=LINE('',#258,#263);
#263=VECTOR('',#264,3.40872929459724);
#263=VECTOR('',#264,3.2957942432711);
#264=DIRECTION('',(-1.0,0.0,0.0));
#255=ORIENTED_EDGE('',*,*,#256,.F.);
#268=CARTESIAN_POINT('',(1.704,0.993,0.813));
#268=CARTESIAN_POINT('',(1.648,0.937,1.143));
#267=VERTEX_POINT('',#268);
#266=EDGE_CURVE('',#267,#257,#271,.T.);
#271=LINE('',#268,#273);
#273=VECTOR('',#274,1.98632929459724);
#273=VECTOR('',#274,1.8733942432711);
#274=DIRECTION('',(0.0,-1.0,0.0));
#265=ORIENTED_EDGE('',*,*,#266,.F.);
#278=CARTESIAN_POINT('',(-1.704,0.993,0.813));
#278=CARTESIAN_POINT('',(-1.648,0.937,1.143));
#277=VERTEX_POINT('',#278);
#276=EDGE_CURVE('',#277,#267,#281,.T.);
#281=LINE('',#278,#283);
#283=VECTOR('',#284,3.40872929459724);
#283=VECTOR('',#284,3.2957942432711);
#284=DIRECTION('',(1.0,0.0,0.0));
#275=ORIENTED_EDGE('',*,*,#276,.F.);
#286=EDGE_CURVE('',#259,#277,#291,.T.);
#291=LINE('',#260,#293);
#293=VECTOR('',#294,1.98632929459724);
#293=VECTOR('',#294,1.8733942432711);
#294=DIRECTION('',(0.0,1.0,0.0));
#285=ORIENTED_EDGE('',*,*,#286,.F.);
#295=STYLED_ITEM('',(#43),#296);
@ -335,13 +335,13 @@ DATA;
#353=ORIENTED_EDGE('',*,*,#354,.F.);
#364=EDGE_CURVE('',#257,#355,#369,.T.);
#369=LINE('',#258,#371);
#371=VECTOR('',#372,0.595815387993105);
#371=VECTOR('',#372,0.935534688164611);
#372=DIRECTION('',(0.166,-0.166,-0.972));
#363=ORIENTED_EDGE('',*,*,#364,.F.);
#373=ORIENTED_EDGE('',*,*,#256,.T.);
#384=EDGE_CURVE('',#357,#259,#389,.T.);
#389=LINE('',#358,#391);
#391=VECTOR('',#392,0.595815387993105);
#391=VECTOR('',#392,0.935534688164611);
#392=DIRECTION('',(0.166,0.166,0.972));
#383=ORIENTED_EDGE('',*,*,#384,.F.);
#393=STYLED_ITEM('',(#43),#394);
@ -362,7 +362,7 @@ DATA;
#402=ORIENTED_EDGE('',*,*,#403,.F.);
#413=EDGE_CURVE('',#267,#404,#418,.T.);
#418=LINE('',#268,#420);
#420=VECTOR('',#421,0.595815387993105);
#420=VECTOR('',#421,0.935534688164611);
#421=DIRECTION('',(0.166,0.166,-0.972));
#412=ORIENTED_EDGE('',*,*,#413,.F.);
#422=ORIENTED_EDGE('',*,*,#266,.T.);
@ -385,7 +385,7 @@ DATA;
#451=ORIENTED_EDGE('',*,*,#452,.F.);
#462=EDGE_CURVE('',#277,#453,#467,.T.);
#467=LINE('',#278,#469);
#469=VECTOR('',#470,0.595815387993105);
#469=VECTOR('',#470,0.935534688164611);
#470=DIRECTION('',(-0.166,0.166,-0.972));
#461=ORIENTED_EDGE('',*,*,#462,.F.);
#471=ORIENTED_EDGE('',*,*,#276,.T.);
@ -481,21 +481,21 @@ DATA;
#936=ADVANCED_FACE('',(#942),#937,.T.);
#937=PLANE('',#938);
#938=AXIS2_PLACEMENT_3D('',#939,#940,#941);
#939=CARTESIAN_POINT('',(-2.007,-0.673,0.0));
#939=CARTESIAN_POINT('',(-1.994,-0.673,0.0));
#940=DIRECTION('',(0.0,-1.0,0.0));
#941=DIRECTION('',(0.,0.,1.));
#942=FACE_OUTER_BOUND('',#943,.T.);
#943=EDGE_LOOP('',(#944,#954,#964,#974,#984,#994));
#947=CARTESIAN_POINT('',(0.584,-0.673,0.0));
#947=CARTESIAN_POINT('',(0.597,-0.673,0.0));
#946=VERTEX_POINT('',#947);
#949=CARTESIAN_POINT('',(-2.007,-0.673,0.0));
#949=CARTESIAN_POINT('',(-1.994,-0.673,0.0));
#948=VERTEX_POINT('',#949);
#945=EDGE_CURVE('',#946,#948,#950,.T.);
#950=LINE('',#947,#952);
#952=VECTOR('',#953,2.5908);
#953=DIRECTION('',(-1.0,0.0,0.0));
#944=ORIENTED_EDGE('',*,*,#945,.F.);
#957=CARTESIAN_POINT('',(0.584,-0.673,0.051));
#957=CARTESIAN_POINT('',(0.597,-0.673,0.051));
#956=VERTEX_POINT('',#957);
#955=EDGE_CURVE('',#956,#946,#960,.T.);
#960=LINE('',#957,#962);
@ -506,7 +506,7 @@ DATA;
#966=VERTEX_POINT('',#967);
#965=EDGE_CURVE('',#966,#956,#970,.T.);
#970=LINE('',#967,#972);
#972=VECTOR('',#973,2.3876);
#972=VECTOR('',#973,2.4003);
#973=DIRECTION('',(1.0,0.0,0.0));
#964=ORIENTED_EDGE('',*,*,#965,.F.);
#977=CARTESIAN_POINT('',(-1.803,-0.673,0.152));
@ -516,11 +516,11 @@ DATA;
#982=VECTOR('',#983,0.1016);
#983=DIRECTION('',(0.0,0.0,-1.0));
#974=ORIENTED_EDGE('',*,*,#975,.F.);
#987=CARTESIAN_POINT('',(-2.007,-0.673,0.152));
#987=CARTESIAN_POINT('',(-1.994,-0.673,0.152));
#986=VERTEX_POINT('',#987);
#985=EDGE_CURVE('',#986,#976,#990,.T.);
#990=LINE('',#987,#992);
#992=VECTOR('',#993,0.2032);
#992=VECTOR('',#993,0.1905);
#993=DIRECTION('',(1.0,0.0,0.0));
#984=ORIENTED_EDGE('',*,*,#985,.F.);
#995=EDGE_CURVE('',#948,#986,#1000,.T.);
@ -532,21 +532,21 @@ DATA;
#1005=ADVANCED_FACE('',(#1011),#1006,.T.);
#1006=PLANE('',#1007);
#1007=AXIS2_PLACEMENT_3D('',#1008,#1009,#1010);
#1008=CARTESIAN_POINT('',(0.584,0.673,0.0));
#1008=CARTESIAN_POINT('',(0.597,0.673,0.0));
#1009=DIRECTION('',(0.0,1.0,0.0));
#1010=DIRECTION('',(0.,0.,1.));
#1011=FACE_OUTER_BOUND('',#1012,.T.);
#1012=EDGE_LOOP('',(#1013,#1023,#1033,#1043,#1053,#1063));
#1016=CARTESIAN_POINT('',(-2.007,0.673,0.0));
#1016=CARTESIAN_POINT('',(-1.994,0.673,0.0));
#1015=VERTEX_POINT('',#1016);
#1018=CARTESIAN_POINT('',(0.584,0.673,0.0));
#1018=CARTESIAN_POINT('',(0.597,0.673,0.0));
#1017=VERTEX_POINT('',#1018);
#1014=EDGE_CURVE('',#1015,#1017,#1019,.T.);
#1019=LINE('',#1016,#1021);
#1021=VECTOR('',#1022,2.5908);
#1022=DIRECTION('',(1.0,0.0,0.0));
#1013=ORIENTED_EDGE('',*,*,#1014,.F.);
#1026=CARTESIAN_POINT('',(-2.007,0.673,0.152));
#1026=CARTESIAN_POINT('',(-1.994,0.673,0.152));
#1025=VERTEX_POINT('',#1026);
#1024=EDGE_CURVE('',#1025,#1015,#1029,.T.);
#1029=LINE('',#1026,#1031);
@ -557,7 +557,7 @@ DATA;
#1035=VERTEX_POINT('',#1036);
#1034=EDGE_CURVE('',#1035,#1025,#1039,.T.);
#1039=LINE('',#1036,#1041);
#1041=VECTOR('',#1042,0.2032);
#1041=VECTOR('',#1042,0.1905);
#1042=DIRECTION('',(-1.0,0.0,0.0));
#1033=ORIENTED_EDGE('',*,*,#1034,.F.);
#1046=CARTESIAN_POINT('',(-1.803,0.673,0.051));
@ -567,11 +567,11 @@ DATA;
#1051=VECTOR('',#1052,0.1016);
#1052=DIRECTION('',(0.0,0.0,1.0));
#1043=ORIENTED_EDGE('',*,*,#1044,.F.);
#1056=CARTESIAN_POINT('',(0.584,0.673,0.051));
#1056=CARTESIAN_POINT('',(0.597,0.673,0.051));
#1055=VERTEX_POINT('',#1056);
#1054=EDGE_CURVE('',#1055,#1045,#1059,.T.);
#1059=LINE('',#1056,#1061);
#1061=VECTOR('',#1062,2.3876);
#1061=VECTOR('',#1062,2.4003);
#1062=DIRECTION('',(-1.0,0.0,0.0));
#1053=ORIENTED_EDGE('',*,*,#1054,.F.);
#1064=EDGE_CURVE('',#1017,#1055,#1069,.T.);
@ -583,7 +583,7 @@ DATA;
#1074=ADVANCED_FACE('',(#1080),#1075,.T.);
#1075=PLANE('',#1076);
#1076=AXIS2_PLACEMENT_3D('',#1077,#1078,#1079);
#1077=CARTESIAN_POINT('',(-2.007,-0.673,0.152));
#1077=CARTESIAN_POINT('',(-1.994,-0.673,0.152));
#1078=DIRECTION('',(0.0,0.0,1.0));
#1079=DIRECTION('',(0.,1.,0.));
#1080=FACE_OUTER_BOUND('',#1081,.T.);
@ -625,7 +625,7 @@ DATA;
#1172=ADVANCED_FACE('',(#1178),#1173,.T.);
#1173=PLANE('',#1174);
#1174=AXIS2_PLACEMENT_3D('',#1175,#1176,#1177);
#1175=CARTESIAN_POINT('',(-2.007,0.673,0.0));
#1175=CARTESIAN_POINT('',(-1.994,0.673,0.0));
#1176=DIRECTION('',(-1.0,0.0,0.0));
#1177=DIRECTION('',(0.,0.,1.));
#1178=FACE_OUTER_BOUND('',#1179,.T.);
@ -642,7 +642,7 @@ DATA;
#1221=ADVANCED_FACE('',(#1227),#1222,.T.);
#1222=PLANE('',#1223);
#1223=AXIS2_PLACEMENT_3D('',#1224,#1225,#1226);
#1224=CARTESIAN_POINT('',(0.584,-0.673,0.0));
#1224=CARTESIAN_POINT('',(0.597,-0.673,0.0));
#1225=DIRECTION('',(1.0,0.0,0.0));
#1226=DIRECTION('',(0.,0.,1.));
#1227=FACE_OUTER_BOUND('',#1228,.T.);
@ -672,7 +672,7 @@ DATA;
#1319=ADVANCED_FACE('',(#1325),#1320,.T.);
#1320=PLANE('',#1321);
#1321=AXIS2_PLACEMENT_3D('',#1322,#1323,#1324);
#1322=CARTESIAN_POINT('',(-2.007,0.673,0.0));
#1322=CARTESIAN_POINT('',(-1.994,0.673,0.0));
#1323=DIRECTION('',(0.0,0.0,-1.0));
#1324=DIRECTION('',(0.,1.,0.));
#1325=FACE_OUTER_BOUND('',#1326,.T.);
@ -687,21 +687,21 @@ DATA;
#1370=ADVANCED_FACE('',(#1376),#1371,.T.);
#1371=PLANE('',#1372);
#1372=AXIS2_PLACEMENT_3D('',#1373,#1374,#1375);
#1373=CARTESIAN_POINT('',(2.007,0.457,0.0));
#1373=CARTESIAN_POINT('',(1.994,0.457,0.0));
#1374=DIRECTION('',(0.0,1.0,0.0));
#1375=DIRECTION('',(0.,0.,1.));
#1376=FACE_OUTER_BOUND('',#1377,.T.);
#1377=EDGE_LOOP('',(#1378,#1388,#1398,#1408,#1418,#1428));
#1381=CARTESIAN_POINT('',(1.194,0.457,0.0));
#1381=CARTESIAN_POINT('',(1.181,0.457,0.0));
#1380=VERTEX_POINT('',#1381);
#1383=CARTESIAN_POINT('',(2.007,0.457,0.0));
#1383=CARTESIAN_POINT('',(1.994,0.457,0.0));
#1382=VERTEX_POINT('',#1383);
#1379=EDGE_CURVE('',#1380,#1382,#1384,.T.);
#1384=LINE('',#1381,#1386);
#1386=VECTOR('',#1387,0.8128);
#1387=DIRECTION('',(1.0,0.0,0.0));
#1378=ORIENTED_EDGE('',*,*,#1379,.F.);
#1391=CARTESIAN_POINT('',(1.194,0.457,0.051));
#1391=CARTESIAN_POINT('',(1.181,0.457,0.051));
#1390=VERTEX_POINT('',#1391);
#1389=EDGE_CURVE('',#1390,#1380,#1394,.T.);
#1394=LINE('',#1391,#1396);
@ -712,7 +712,7 @@ DATA;
#1400=VERTEX_POINT('',#1401);
#1399=EDGE_CURVE('',#1400,#1390,#1404,.T.);
#1404=LINE('',#1401,#1406);
#1406=VECTOR('',#1407,0.6096);
#1406=VECTOR('',#1407,0.6223);
#1407=DIRECTION('',(-1.0,0.0,0.0));
#1398=ORIENTED_EDGE('',*,*,#1399,.F.);
#1411=CARTESIAN_POINT('',(1.803,0.457,0.152));
@ -722,11 +722,11 @@ DATA;
#1416=VECTOR('',#1417,0.1016);
#1417=DIRECTION('',(0.0,0.0,-1.0));
#1408=ORIENTED_EDGE('',*,*,#1409,.F.);
#1421=CARTESIAN_POINT('',(2.007,0.457,0.152));
#1421=CARTESIAN_POINT('',(1.994,0.457,0.152));
#1420=VERTEX_POINT('',#1421);
#1419=EDGE_CURVE('',#1420,#1410,#1424,.T.);
#1424=LINE('',#1421,#1426);
#1426=VECTOR('',#1427,0.2032);
#1426=VECTOR('',#1427,0.1905);
#1427=DIRECTION('',(-1.0,0.0,0.0));
#1418=ORIENTED_EDGE('',*,*,#1419,.F.);
#1429=EDGE_CURVE('',#1382,#1420,#1434,.T.);
@ -738,21 +738,21 @@ DATA;
#1439=ADVANCED_FACE('',(#1445),#1440,.T.);
#1440=PLANE('',#1441);
#1441=AXIS2_PLACEMENT_3D('',#1442,#1443,#1444);
#1442=CARTESIAN_POINT('',(1.194,-0.457,0.0));
#1442=CARTESIAN_POINT('',(1.181,-0.457,0.0));
#1443=DIRECTION('',(0.0,-1.0,0.0));
#1444=DIRECTION('',(0.,0.,1.));
#1445=FACE_OUTER_BOUND('',#1446,.T.);
#1446=EDGE_LOOP('',(#1447,#1457,#1467,#1477,#1487,#1497));
#1450=CARTESIAN_POINT('',(2.007,-0.457,0.0));
#1450=CARTESIAN_POINT('',(1.994,-0.457,0.0));
#1449=VERTEX_POINT('',#1450);
#1452=CARTESIAN_POINT('',(1.194,-0.457,0.0));
#1452=CARTESIAN_POINT('',(1.181,-0.457,0.0));
#1451=VERTEX_POINT('',#1452);
#1448=EDGE_CURVE('',#1449,#1451,#1453,.T.);
#1453=LINE('',#1450,#1455);
#1455=VECTOR('',#1456,0.8128);
#1456=DIRECTION('',(-1.0,0.0,0.0));
#1447=ORIENTED_EDGE('',*,*,#1448,.F.);
#1460=CARTESIAN_POINT('',(2.007,-0.457,0.152));
#1460=CARTESIAN_POINT('',(1.994,-0.457,0.152));
#1459=VERTEX_POINT('',#1460);
#1458=EDGE_CURVE('',#1459,#1449,#1463,.T.);
#1463=LINE('',#1460,#1465);
@ -763,7 +763,7 @@ DATA;
#1469=VERTEX_POINT('',#1470);
#1468=EDGE_CURVE('',#1469,#1459,#1473,.T.);
#1473=LINE('',#1470,#1475);
#1475=VECTOR('',#1476,0.2032);
#1475=VECTOR('',#1476,0.1905);
#1476=DIRECTION('',(1.0,0.0,0.0));
#1467=ORIENTED_EDGE('',*,*,#1468,.F.);
#1480=CARTESIAN_POINT('',(1.803,-0.457,0.051));
@ -773,11 +773,11 @@ DATA;
#1485=VECTOR('',#1486,0.1016);
#1486=DIRECTION('',(0.0,0.0,1.0));
#1477=ORIENTED_EDGE('',*,*,#1478,.F.);
#1490=CARTESIAN_POINT('',(1.194,-0.457,0.051));
#1490=CARTESIAN_POINT('',(1.181,-0.457,0.051));
#1489=VERTEX_POINT('',#1490);
#1488=EDGE_CURVE('',#1489,#1479,#1493,.T.);
#1493=LINE('',#1490,#1495);
#1495=VECTOR('',#1496,0.6096);
#1495=VECTOR('',#1496,0.6223);
#1496=DIRECTION('',(1.0,0.0,0.0));
#1487=ORIENTED_EDGE('',*,*,#1488,.F.);
#1498=EDGE_CURVE('',#1451,#1489,#1503,.T.);
@ -789,7 +789,7 @@ DATA;
#1508=ADVANCED_FACE('',(#1514),#1509,.T.);
#1509=PLANE('',#1510);
#1510=AXIS2_PLACEMENT_3D('',#1511,#1512,#1513);
#1511=CARTESIAN_POINT('',(2.007,0.457,0.152));
#1511=CARTESIAN_POINT('',(1.994,0.457,0.152));
#1512=DIRECTION('',(0.0,0.0,1.0));
#1513=DIRECTION('',(0.,1.,0.));
#1514=FACE_OUTER_BOUND('',#1515,.T.);
@ -831,7 +831,7 @@ DATA;
#1606=ADVANCED_FACE('',(#1612),#1607,.T.);
#1607=PLANE('',#1608);
#1608=AXIS2_PLACEMENT_3D('',#1609,#1610,#1611);
#1609=CARTESIAN_POINT('',(2.007,-0.457,0.0));
#1609=CARTESIAN_POINT('',(1.994,-0.457,0.0));
#1610=DIRECTION('',(1.0,0.0,0.0));
#1611=DIRECTION('',(0.,0.,1.));
#1612=FACE_OUTER_BOUND('',#1613,.T.);
@ -848,7 +848,7 @@ DATA;
#1655=ADVANCED_FACE('',(#1661),#1656,.T.);
#1656=PLANE('',#1657);
#1657=AXIS2_PLACEMENT_3D('',#1658,#1659,#1660);
#1658=CARTESIAN_POINT('',(1.194,0.457,0.0));
#1658=CARTESIAN_POINT('',(1.181,0.457,0.0));
#1659=DIRECTION('',(-1.0,0.0,0.0));
#1660=DIRECTION('',(0.,0.,1.));
#1661=FACE_OUTER_BOUND('',#1662,.T.);
@ -878,7 +878,7 @@ DATA;
#1753=ADVANCED_FACE('',(#1759),#1754,.T.);
#1754=PLANE('',#1755);
#1755=AXIS2_PLACEMENT_3D('',#1756,#1757,#1758);
#1756=CARTESIAN_POINT('',(2.007,-0.457,0.0));
#1756=CARTESIAN_POINT('',(1.994,-0.457,0.0));
#1757=DIRECTION('',(0.0,0.0,-1.0));
#1758=DIRECTION('',(0.,1.,0.));
#1759=FACE_OUTER_BOUND('',#1760,.T.);

View File

@ -0,0 +1,825 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION (( 'STEP AP214' ),
'1' );
FILE_NAME ('MSMP package.STEP',
'2016-08-19T07:07:45',
( 'Vishay' ),
( '' ),
'SwSTEP 2.0',
'SolidWorks 2015',
'' );
FILE_SCHEMA (( 'AUTOMOTIVE_DESIGN' ));
ENDSEC;
DATA;
#1 = ORIENTED_EDGE ( 'NONE', *, *, #448, .F. ) ;
#2 = AXIS2_PLACEMENT_3D ( 'NONE', #418, #662, #437 ) ;
#3 = ORIENTED_EDGE ( 'NONE', *, *, #229, .F. ) ;
#4 = EDGE_CURVE ( 'NONE', #472, #571, #92, .T. ) ;
#5 = VECTOR ( 'NONE', #261, 1000.000000000000000 ) ;
#6 = LINE ( 'NONE', #32, #480 ) ;
#7 =( LENGTH_UNIT ( ) NAMED_UNIT ( * ) SI_UNIT ( .MILLI., .METRE. ) );
#8 = PLANE ( 'NONE', #74 ) ;
#9 = EDGE_LOOP ( 'NONE', ( #17, #3, #694, #116 ) ) ;
#10 =( LENGTH_UNIT ( ) NAMED_UNIT ( * ) SI_UNIT ( .MILLI., .METRE. ) );
#11 = LINE ( 'NONE', #377, #158 ) ;
#12 = LINE ( 'NONE', #451, #492 ) ;
#13 = AXIS2_PLACEMENT_3D ( 'NONE', #105, #604, #224 ) ;
#14 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
#15 = CARTESIAN_POINT ( 'NONE', ( 0.6302161917152264700, 0.05618686558523977600, 0.8576654041047709900 ) ) ;
#16 = ORIENTED_EDGE ( 'NONE', *, *, #606, .T. ) ;
#17 = ORIENTED_EDGE ( 'NONE', *, *, #108, .T. ) ;
#18 = SURFACE_STYLE_FILL_AREA ( #489 ) ;
#19 = ORIENTED_EDGE ( 'NONE', *, *, #4, .T. ) ;
#20 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -1.000000000000000000, -0.0000000000000000000 ) ) ;
#21 = CARTESIAN_POINT ( 'NONE', ( -1.719783808284775000, 0.05618686558523977600, -0.2023345958952274500 ) ) ;
#22 = EDGE_CURVE ( 'NONE', #321, #188, #268, .T. ) ;
#23 = PRESENTATION_STYLE_ASSIGNMENT (( #611 ) ) ;
#24 = PLANE ( 'NONE', #501 ) ;
#25 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
#26 = LINE ( 'NONE', #221, #329 ) ;
#27 = FILL_AREA_STYLE_COLOUR ( '', #569 ) ;
#28 = EDGE_CURVE ( 'NONE', #179, #500, #250, .T. ) ;
#29 = SURFACE_SIDE_STYLE ('',( #661 ) ) ;
#30 = CARTESIAN_POINT ( 'NONE', ( 0.6170928921863383400, -0.09381313441476020500, 0.8576654041047709900 ) ) ;
#31 = ORIENTED_EDGE ( 'NONE', *, *, #290, .T. ) ;
#32 = CARTESIAN_POINT ( 'NONE', ( -1.523414816616035900, 0.5861868655852398000, -0.4423345958952280600 ) ) ;
#33 =( GEOMETRIC_REPRESENTATION_CONTEXT ( 3 ) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT ( ( #704 ) ) GLOBAL_UNIT_ASSIGNED_CONTEXT ( ( #7, #367, #750 ) ) REPRESENTATION_CONTEXT ( 'NONE', 'WORKASPACE' ) );
#34 = ADVANCED_FACE ( 'NONE', ( #752 ), #453, .F. ) ;
#35 = CARTESIAN_POINT ( 'NONE', ( -1.569783808284774200, 0.5861868655852398000, 0.8112964124360327900 ) ) ;
#36 = LINE ( 'NONE', #141, #539 ) ;
#37 = PLANE ( 'NONE', #2 ) ;
#38 = EDGE_LOOP ( 'NONE', ( #120, #238, #62, #493, #432, #184, #715, #299, #529, #31, #698, #385 ) ) ;
#39 = CARTESIAN_POINT ( 'NONE', ( 0.1802161917152239600, -0.09381313441476020500, -0.09233459589523193600 ) ) ;
#40 = ORIENTED_EDGE ( 'NONE', *, *, #85, .F. ) ;
#41 = UNCERTAINTY_MEASURE_WITH_UNIT (LENGTH_MEASURE( 1.000000000000000100E-005 ), #708, 'distance_accuracy_value', 'NONE');
#42 = FACE_OUTER_BOUND ( 'NONE', #277, .T. ) ;
#43 = ORIENTED_EDGE ( 'NONE', *, *, #85, .T. ) ;
#44 = CARTESIAN_POINT ( 'NONE', ( -1.719783808284775000, 0.05618686558523977600, 0.6176654041047668900 ) ) ;
#45 = PRESENTATION_LAYER_ASSIGNMENT ( '', '', ( #762 ) ) ;
#46 = DIRECTION ( 'NONE', ( -0.08715574274765836000, 0.9961946980917455500, -5.832412861803903500E-018 ) ) ;
#47 = FACE_OUTER_BOUND ( 'NONE', #779, .T. ) ;
#48 = EDGE_CURVE ( 'NONE', #571, #559, #487, .T. ) ;
#49 = CARTESIAN_POINT ( 'NONE', ( 0.6170928921863383400, -0.09381313441476020500, -0.09233459589523193600 ) ) ;
#50 = DIRECTION ( 'NONE', ( 0.08682659386424779200, -0.9924325091389669700, 0.08682659386424779200 ) ) ;
#51 = ORIENTED_EDGE ( 'NONE', *, *, #707, .T. ) ;
#52 = ADVANCED_FACE ( 'NONE', ( #310 ), #172, .T. ) ;
#53 = CARTESIAN_POINT ( 'NONE', ( -1.569783808284774200, -0.09381313441476020500, 0.8445421045758828600 ) ) ;
#54 = UNCERTAINTY_MEASURE_WITH_UNIT (LENGTH_MEASURE( 1.000000000000000100E-005 ), #231, 'distance_accuracy_value', 'NONE');
#55 = ORIENTED_EDGE ( 'NONE', *, *, #423, .F. ) ;
#56 = CARTESIAN_POINT ( 'NONE', ( -1.559983293662088500, -0.05583352914605126800, 0.8478648894820854900 ) ) ;
#57 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
#58 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
#59 = LINE ( 'NONE', #516, #697 ) ;
#60 = ORIENTED_EDGE ( 'NONE', *, *, #586, .F. ) ;
#61 = STYLED_ITEM ( 'NONE', ( #23 ), #722 ) ;
#62 = ORIENTED_EDGE ( 'NONE', *, *, #625, .T. ) ;
#63 = AXIS2_PLACEMENT_3D ( 'NONE', #21, #378, #764 ) ;
#64 = EDGE_CURVE ( 'NONE', #545, #435, #150, .T. ) ;
#65 =( NAMED_UNIT ( * ) PLANE_ANGLE_UNIT ( ) SI_UNIT ( $, .RADIAN. ) );
#66 = ORIENTED_EDGE ( 'NONE', *, *, #797, .T. ) ;
#67 = FILL_AREA_STYLE_COLOUR ( '', #144 ) ;
#68 = EDGE_CURVE ( 'NONE', #421, #321, #460, .T. ) ;
#69 = ORIENTED_EDGE ( 'NONE', *, *, #554, .F. ) ;
#70 = CARTESIAN_POINT ( 'NONE', ( 0.03021619171522313400, 0.05618686558523977600, -0.09233459589523193600 ) ) ;
#71 = COLOUR_RGB ( '',0.5372549019607842900, 0.3490196078431372400, 0.3372549019607843400 ) ;
#72 = SURFACE_SIDE_STYLE ('',( #112 ) ) ;
#73 = VERTEX_POINT ( 'NONE', #612 ) ;
#74 = AXIS2_PLACEMENT_3D ( 'NONE', #127, #457, #392 ) ;
#75 = CARTESIAN_POINT ( 'NONE', ( 0.6302161917152264700, 0.05618686558523977600, 0.5076654041047713500 ) ) ;
#76 = CARTESIAN_POINT ( 'NONE', ( -1.569783808284774200, 0.05618686558523977600, -0.4423345958952280600 ) ) ;
#77 = EDGE_LOOP ( 'NONE', ( #765, #51, #680, #469 ) ) ;
#78 = DIRECTION ( 'NONE', ( -0.9961946980917455500, -0.08715574274765836000, 0.0000000000000000000 ) ) ;
#79 =( NAMED_UNIT ( * ) SI_UNIT ( $, .STERADIAN. ) SOLID_ANGLE_UNIT ( ) );
#80 = LINE ( 'NONE', #191, #401 ) ;
#81 = VECTOR ( 'NONE', #718, 1000.000000000000000 ) ;
#82 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION ( '', ( #762 ), #123 ) ;
#83 = CARTESIAN_POINT ( 'NONE', ( 0.6170928921863383400, -0.09381313441476023300, 0.8445421045758828600 ) ) ;
#84 = DIRECTION ( 'NONE', ( 0.08715574274765836000, -0.9961946980917455500, 0.0000000000000000000 ) ) ;
#85 = EDGE_CURVE ( 'NONE', #685, #441, #619, .T. ) ;
#86 = ORIENTED_EDGE ( 'NONE', *, *, #108, .F. ) ;
#87 = FILL_AREA_STYLE_COLOUR ( '', #71 ) ;
#88 = VECTOR ( 'NONE', #714, 1000.000000000000000 ) ;
#89 =( NAMED_UNIT ( * ) PLANE_ANGLE_UNIT ( ) SI_UNIT ( $, .RADIAN. ) );
#90 = EDGE_LOOP ( 'NONE', ( #131, #394, #181, #223 ) ) ;
#91 = CARTESIAN_POINT ( 'NONE', ( 0.6302161917152264700, 0.05618686558523977600, -0.4423345958952280600 ) ) ;
#92 = LINE ( 'NONE', #214, #412 ) ;
#93 = FACE_OUTER_BOUND ( 'NONE', #342, .T. ) ;
#94 =( LENGTH_UNIT ( ) NAMED_UNIT ( * ) SI_UNIT ( .MILLI., .METRE. ) );
#95 = PRESENTATION_STYLE_ASSIGNMENT (( #326 ) ) ;
#96 = ORIENTED_EDGE ( 'NONE', *, *, #442, .T. ) ;
#97 = FILL_AREA_STYLE ('',( #27 ) ) ;
#98 =( NAMED_UNIT ( * ) PLANE_ANGLE_UNIT ( ) SI_UNIT ( $, .RADIAN. ) );
#99 = CARTESIAN_POINT ( 'NONE', ( -1.569783808284774200, 0.05618686558523977600, 0.6176654041047668900 ) ) ;
#100 =( NAMED_UNIT ( * ) PLANE_ANGLE_UNIT ( ) SI_UNIT ( $, .RADIAN. ) );
#101 = COLOUR_RGB ( '',0.5372549019607842900, 0.3490196078431372400, 0.3372549019607843400 ) ;
#102 = EDGE_LOOP ( 'NONE', ( #202, #406, #744, #553 ) ) ;
#103 = AXIS2_PLACEMENT_3D ( 'NONE', #403, #474, #109 ) ;
#104 = UNCERTAINTY_MEASURE_WITH_UNIT (LENGTH_MEASURE( 1.000000000000000100E-005 ), #581, 'distance_accuracy_value', 'NONE');
#105 = CARTESIAN_POINT ( 'NONE', ( -1.569783808284774200, 0.05618686558523977600, 0.8576654041047709900 ) ) ;
#106 = CARTESIAN_POINT ( 'NONE', ( -1.719783808284775000, 0.05618686558523977600, -0.2023345958952274500 ) ) ;
#107 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
#108 = EDGE_CURVE ( 'NONE', #500, #73, #778, .T. ) ;
#109 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.9961946980917455500, -0.08715574274765836000 ) ) ;
#110 = VECTOR ( 'NONE', #463, 1000.000000000000100 ) ;
#111 = PLANE ( 'NONE', #732 ) ;
#112 = SURFACE_STYLE_FILL_AREA ( #509 ) ;
#113 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -0.08715574274765836000, 0.9961946980917455500 ) ) ;
#114 = VECTOR ( 'NONE', #286, 1000.000000000000000 ) ;
#115 = ADVANCED_FACE ( 'NONE', ( #757 ), #111, .T. ) ;
#116 = ORIENTED_EDGE ( 'NONE', *, *, #28, .T. ) ;
#117 = FILL_AREA_STYLE_COLOUR ( '', #668 ) ;
#118 = SURFACE_SIDE_STYLE ('',( #18 ) ) ;
#119 = VECTOR ( 'NONE', #404, 1000.000000000000000 ) ;
#120 = ORIENTED_EDGE ( 'NONE', *, *, #398, .F. ) ;
#121 = ORIENTED_EDGE ( 'NONE', *, *, #531, .F. ) ;
#122 = PRESENTATION_STYLE_ASSIGNMENT (( #340 ) ) ;
#123 =( GEOMETRIC_REPRESENTATION_CONTEXT ( 3 ) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT ( ( #182 ) ) GLOBAL_UNIT_ASSIGNED_CONTEXT ( ( #530, #89, #450 ) ) REPRESENTATION_CONTEXT ( 'NONE', 'WORKASPACE' ) );
#124 = EDGE_CURVE ( 'NONE', #372, #338, #300, .T. ) ;
#125 = SURFACE_STYLE_USAGE ( .BOTH. , #573 ) ;
#126 = LINE ( 'NONE', #608, #613 ) ;
#127 = CARTESIAN_POINT ( 'NONE', ( 0.6302161917152264700, 0.05618686558523977600, 0.8576654041047709900 ) ) ;
#128 = SHAPE_DEFINITION_REPRESENTATION ( #241, #355 ) ;
#129 = CARTESIAN_POINT ( 'NONE', ( -1.569783808284774200, 0.05618686558523977600, -0.4423345958952280600 ) ) ;
#130 = VECTOR ( 'NONE', #411, 1000.000000000000000 ) ;
#131 = ORIENTED_EDGE ( 'NONE', *, *, #22, .T. ) ;
#132 =( NAMED_UNIT ( * ) PLANE_ANGLE_UNIT ( ) SI_UNIT ( $, .RADIAN. ) );
#133 = PRESENTATION_LAYER_ASSIGNMENT ( '', '', ( #61 ) ) ;
#134 = PLANE ( 'NONE', #140 ) ;
#135 = PRESENTATION_STYLE_ASSIGNMENT (( #195 ) ) ;
#136 = LINE ( 'NONE', #331, #304 ) ;
#137 = ORIENTED_EDGE ( 'NONE', *, *, #713, .T. ) ;
#138 =( LENGTH_UNIT ( ) NAMED_UNIT ( * ) SI_UNIT ( .MILLI., .METRE. ) );
#139 = ORIENTED_EDGE ( 'NONE', *, *, #707, .F. ) ;
#140 = AXIS2_PLACEMENT_3D ( 'NONE', #76, #440, #84 ) ;
#141 = CARTESIAN_POINT ( 'NONE', ( 0.6302161917152264700, 0.05618686558523977600, 0.8576654041047709900 ) ) ;
#142 = EDGE_CURVE ( 'NONE', #188, #368, #601, .T. ) ;
#143 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
#144 = COLOUR_RGB ( '',0.7921568627450980000, 0.8196078431372548800, 0.9333333333333333500 ) ;
#145 = VECTOR ( 'NONE', #660, 1000.000000000000000 ) ;
#146 = SURFACE_STYLE_USAGE ( .BOTH. , #594 ) ;
#147 = DIRECTION ( 'NONE', ( -0.08682659386424779200, -0.9924325091389669700, 0.08682659386424779200 ) ) ;
#148 =( NAMED_UNIT ( * ) SI_UNIT ( $, .STERADIAN. ) SOLID_ANGLE_UNIT ( ) );
#149 = VERTEX_POINT ( 'NONE', #550 ) ;
#150 = LINE ( 'NONE', #678, #802 ) ;
#151 = ORIENTED_EDGE ( 'NONE', *, *, #142, .F. ) ;
#152 = SURFACE_STYLE_USAGE ( .BOTH. , #537 ) ;
#153 = SURFACE_SIDE_STYLE ('',( #405 ) ) ;
#154 = STYLED_ITEM ( 'NONE', ( #630 ), #355 ) ;
#155 = DIRECTION ( 'NONE', ( 1.000000000000000000, -0.0000000000000000000, -0.0000000000000000000 ) ) ;
#156 = CARTESIAN_POINT ( 'NONE', ( 0.6302161917152264700, 0.05618686558523977600, 0.8576654041047709900 ) ) ;
#157 = ADVANCED_FACE ( 'NONE', ( #419 ), #164, .F. ) ;
#158 = VECTOR ( 'NONE', #20, 1000.000000000000000 ) ;
#159 = LINE ( 'NONE', #438, #640 ) ;
#160 = VERTEX_POINT ( 'NONE', #549 ) ;
#161 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 5.854691731421723900E-018, 1.000000000000000000 ) ) ;
#162 = ORIENTED_EDGE ( 'NONE', *, *, #766, .T. ) ;
#163 = CARTESIAN_POINT ( 'NONE', ( 0.6302161917152264700, 0.05618686558523977600, -0.4423345958952280600 ) ) ;
#164 = PLANE ( 'NONE', #375 ) ;
#165 = EDGE_LOOP ( 'NONE', ( #602, #562, #741, #175 ) ) ;
#166 = CARTESIAN_POINT ( 'NONE', ( 0.6302161917152264700, -0.09381313441476020500, -0.4292112963663399800 ) ) ;
#167 = EDGE_CURVE ( 'NONE', #472, #253, #201, .T. ) ;
#168 = LINE ( 'NONE', #166, #672 ) ;
#169 = SURFACE_SIDE_STYLE ('',( #186 ) ) ;
#170 = VECTOR ( 'NONE', #155, 1000.000000000000000 ) ;
#171 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
#172 = PLANE ( 'NONE', #650 ) ;
#173 = ADVANCED_FACE ( 'NONE', ( #42 ), #302, .T. ) ;
#174 =( LENGTH_UNIT ( ) NAMED_UNIT ( * ) SI_UNIT ( .MILLI., .METRE. ) );
#175 = ORIENTED_EDGE ( 'NONE', *, *, #247, .T. ) ;
#176 = FACE_OUTER_BOUND ( 'NONE', #90, .T. ) ;
#177 = LINE ( 'NONE', #641, #806 ) ;
#178 = FACE_OUTER_BOUND ( 'NONE', #38, .T. ) ;
#179 = VERTEX_POINT ( 'NONE', #564 ) ;
#180 = CARTESIAN_POINT ( 'NONE', ( -1.569783808284774200, 0.05618686558523977600, -0.4423345958952280600 ) ) ;
#181 = ORIENTED_EDGE ( 'NONE', *, *, #265, .F. ) ;
#182 = UNCERTAINTY_MEASURE_WITH_UNIT (LENGTH_MEASURE( 1.000000000000000100E-005 ), #530, 'distance_accuracy_value', 'NONE');
#183 = VECTOR ( 'NONE', #486, 1000.000000000000000 ) ;
#184 = ORIENTED_EDGE ( 'NONE', *, *, #68, .F. ) ;
#185 = EDGE_CURVE ( 'NONE', #435, #284, #361, .T. ) ;
#186 = SURFACE_STYLE_FILL_AREA ( #552 ) ;
#187 = FILL_AREA_STYLE ('',( #67 ) ) ;
#188 = VERTEX_POINT ( 'NONE', #359 ) ;
#189 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, -0.0000000000000000000 ) ) ;
#190 = LINE ( 'NONE', #70, #510 ) ;
#191 = CARTESIAN_POINT ( 'NONE', ( 0.6170928921863383400, -0.09381313441476020500, 0.8576654041047709900 ) ) ;
#192 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
#193 = PRESENTATION_LAYER_ASSIGNMENT ( '', '', ( #154 ) ) ;
#194 = LINE ( 'NONE', #712, #316 ) ;
#195 = SURFACE_STYLE_USAGE ( .BOTH. , #638 ) ;
#196 = ORIENTED_EDGE ( 'NONE', *, *, #327, .F. ) ;
#197 = PRODUCT ( 'MSMP package', 'MSMP package', '', ( #308 ) ) ;
#198 = EDGE_LOOP ( 'NONE', ( #785, #563, #19, #215 ) ) ;
#199 = DIRECTION ( 'NONE', ( 1.000000000000000000, -0.0000000000000000000, -0.0000000000000000000 ) ) ;
#200 = CARTESIAN_POINT ( 'NONE', ( -1.719783808284775000, 0.05618686558523977600, 0.6176654041047668900 ) ) ;
#201 = LINE ( 'NONE', #358, #521 ) ;
#202 = ORIENTED_EDGE ( 'NONE', *, *, #185, .T. ) ;
#203 = CARTESIAN_POINT ( 'NONE', ( 0.1802161917152204900, -0.09381313441475436200, 0.5076654041047713500 ) ) ;
#204 = SURFACE_SIDE_STYLE ('',( #242 ) ) ;
#205 = VECTOR ( 'NONE', #46, 1000.000000000000000 ) ;
#206 = STYLED_ITEM ( 'NONE', ( #95 ), #294 ) ;
#207 =( NAMED_UNIT ( * ) SI_UNIT ( $, .STERADIAN. ) SOLID_ANGLE_UNIT ( ) );
#208 = EDGE_CURVE ( 'NONE', #149, #685, #341, .T. ) ;
#209 = DIRECTION ( 'NONE', ( 0.08682659386424779200, 0.9924325091389669700, 0.08682659386424779200 ) ) ;
#210 = CARTESIAN_POINT ( 'NONE', ( 0.03021619171521966500, -0.09381313441475436200, -0.09233459589523193600 ) ) ;
#211 = ORIENTED_EDGE ( 'NONE', *, *, #64, .F. ) ;
#212 = FILL_AREA_STYLE ('',( #387 ) ) ;
#213 = PLANE ( 'NONE', #306 ) ;
#214 = CARTESIAN_POINT ( 'NONE', ( -1.553198322000227200, -0.1333861101138683600, -0.4257491096106812400 ) ) ;
#215 = ORIENTED_EDGE ( 'NONE', *, *, #473, .F. ) ;
#216 = CARTESIAN_POINT ( 'NONE', ( -1.569783808284774200, 0.05618686558523978300, 0.6176654041047668900 ) ) ;
#217 =( NAMED_UNIT ( * ) PLANE_ANGLE_UNIT ( ) SI_UNIT ( $, .RADIAN. ) );
#218 = FACE_OUTER_BOUND ( 'NONE', #663, .T. ) ;
#219 = ORIENTED_EDGE ( 'NONE', *, *, #365, .T. ) ;
#220 = ADVANCED_FACE ( 'NONE', ( #499 ), #416, .F. ) ;
#221 = CARTESIAN_POINT ( 'NONE', ( -1.419783808284780200, 0.05618686558523977600, 0.6176654041047668900 ) ) ;
#222 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION ( '', ( #691 ), #259 ) ;
#223 = ORIENTED_EDGE ( 'NONE', *, *, #320, .F. ) ;
#224 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.9961946980917455500, -0.08715574274765836000 ) ) ;
#225 = CARTESIAN_POINT ( 'NONE', ( 0.7802161917152238300, 0.05618686558523977600, -0.09233459589523193600 ) ) ;
#226 = CARTESIAN_POINT ( 'NONE', ( -1.719783808284775000, -0.09381313441476020500, 0.6176654041047668900 ) ) ;
#227 = AXIS2_PLACEMENT_3D ( 'NONE', #447, #528, #143 ) ;
#228 = FACE_OUTER_BOUND ( 'NONE', #397, .T. ) ;
#229 = EDGE_CURVE ( 'NONE', #317, #73, #427, .T. ) ;
#230 = EDGE_LOOP ( 'NONE', ( #66, #461, #598, #588 ) ) ;
#231 =( LENGTH_UNIT ( ) NAMED_UNIT ( * ) SI_UNIT ( .MILLI., .METRE. ) );
#232 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
#233 = ORIENTED_EDGE ( 'NONE', *, *, #629, .T. ) ;
#234 = ADVANCED_FACE ( 'NONE', ( #266 ), #37, .T. ) ;
#235 = CARTESIAN_POINT ( 'NONE', ( -1.569783808284774200, 0.05618686558523977600, 0.8576654041047709900 ) ) ;
#236 = SURFACE_STYLE_USAGE ( .BOTH. , #420 ) ;
#237 = VECTOR ( 'NONE', #270, 1000.000000000000000 ) ;
#238 = ORIENTED_EDGE ( 'NONE', *, *, #498, .T. ) ;
#239 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION ( '', ( #154 ), #281 ) ;
#240 = ORIENTED_EDGE ( 'NONE', *, *, #448, .T. ) ;
#241 = PRODUCT_DEFINITION_SHAPE ( 'NONE', 'NONE', #436 ) ;
#242 = SURFACE_STYLE_FILL_AREA ( #646 ) ;
#243 = LINE ( 'NONE', #216, #81 ) ;
#244 = AXIS2_PLACEMENT_3D ( 'NONE', #770, #330, #711 ) ;
#245 = APPLICATION_PROTOCOL_DEFINITION ( 'draft international standard', 'automotive_design', 1998, #690 ) ;
#246 = ORIENTED_EDGE ( 'NONE', *, *, #290, .F. ) ;
#247 = EDGE_CURVE ( 'NONE', #372, #665, #610, .T. ) ;
#248 = LINE ( 'NONE', #446, #88 ) ;
#249 = ORIENTED_EDGE ( 'NONE', *, *, #247, .F. ) ;
#250 = LINE ( 'NONE', #273, #772 ) ;
#251 = COLOUR_RGB ( '',0.5372549019607842900, 0.3490196078431372400, 0.3372549019607843400 ) ;
#252 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
#253 = VERTEX_POINT ( 'NONE', #590 ) ;
#254 = CARTESIAN_POINT ( 'NONE', ( -0.4197838082847724100, -0.09381313441475436200, 0.6176654041047668900 ) ) ;
#255 = CARTESIAN_POINT ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
#256 = FILL_AREA_STYLE ('',( #701 ) ) ;
#257 = VECTOR ( 'NONE', #793, 1000.000000000000000 ) ;
#258 = ORIENTED_EDGE ( 'NONE', *, *, #48, .T. ) ;
#259 =( GEOMETRIC_REPRESENTATION_CONTEXT ( 3 ) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT ( ( #468 ) ) GLOBAL_UNIT_ASSIGNED_CONTEXT ( ( #174, #681, #683 ) ) REPRESENTATION_CONTEXT ( 'NONE', 'WORKASPACE' ) );
#260 = CARTESIAN_POINT ( 'NONE', ( 0.5838472000464882700, 0.5861868655852398000, 0.8112964124360327900 ) ) ;
#261 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
#262 = STYLED_ITEM ( 'NONE', ( #784 ), #543 ) ;
#263 =( NAMED_UNIT ( * ) PLANE_ANGLE_UNIT ( ) SI_UNIT ( $, .RADIAN. ) );
#264 = DIRECTION ( 'NONE', ( -1.000000000000000000, -0.0000000000000000000, -0.0000000000000000000 ) ) ;
#265 = EDGE_CURVE ( 'NONE', #73, #368, #248, .T. ) ;
#266 = FACE_OUTER_BOUND ( 'NONE', #357, .T. ) ;
#267 = CARTESIAN_POINT ( 'NONE', ( -1.719783808284778500, -0.09381313441475439000, -0.2023345958952274500 ) ) ;
#268 = LINE ( 'NONE', #30, #596 ) ;
#269 = FACE_OUTER_BOUND ( 'NONE', #390, .T. ) ;
#270 = DIRECTION ( 'NONE', ( -1.000000000000000000, -1.387778780781445700E-017, 0.0000000000000000000 ) ) ;
#271 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.08715574274765836000, -0.9961946980917455500 ) ) ;
#272 = LINE ( 'NONE', #548, #114 ) ;
#273 = CARTESIAN_POINT ( 'NONE', ( -1.569783808284774200, 0.05618686558523977600, 0.8576654041047709900 ) ) ;
#274 = UNCERTAINTY_MEASURE_WITH_UNIT (LENGTH_MEASURE( 1.000000000000000100E-005 ), #502, 'distance_accuracy_value', 'NONE');
#275 = ORIENTED_EDGE ( 'NONE', *, *, #531, .T. ) ;
#276 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
#277 = EDGE_LOOP ( 'NONE', ( #258, #758, #137, #626, #362, #574 ) ) ;
#278 = ADVANCED_FACE ( 'NONE', ( #409 ), #653, .F. ) ;
#279 = PRESENTATION_STYLE_ASSIGNMENT (( #236 ) ) ;
#280 = ORIENTED_EDGE ( 'NONE', *, *, #407, .F. ) ;
#281 =( GEOMETRIC_REPRESENTATION_CONTEXT ( 3 ) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT ( ( #274 ) ) GLOBAL_UNIT_ASSIGNED_CONTEXT ( ( #502, #65, #428 ) ) REPRESENTATION_CONTEXT ( 'NONE', 'WORKASPACE' ) );
#282 = DIRECTION ( 'NONE', ( 1.000000000000000000, -0.0000000000000000000, -0.0000000000000000000 ) ) ;
#283 = CARTESIAN_POINT ( 'NONE', ( -1.556660508755886000, -0.09381313441476020500, -0.4423345958952280600 ) ) ;
#284 = VERTEX_POINT ( 'NONE', #314 ) ;
#285 = COLOUR_RGB ( '',0.5372549019607842900, 0.3490196078431372400, 0.3372549019607843400 ) ;
#286 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
#287 = EDGE_LOOP ( 'NONE', ( #632, #151, #533, #196 ) ) ;
#288 = PRESENTATION_LAYER_ASSIGNMENT ( '', '', ( #206 ) ) ;
#289 = CARTESIAN_POINT ( 'NONE', ( -1.569783808284774200, 0.05618686558523977600, -0.4423345958952280600 ) ) ;
#290 = EDGE_CURVE ( 'NONE', #160, #253, #738, .T. ) ;
#291 =( NAMED_UNIT ( * ) SI_UNIT ( $, .STERADIAN. ) SOLID_ANGLE_UNIT ( ) );
#292 = LINE ( 'NONE', #614, #429 ) ;
#293 = CARTESIAN_POINT ( 'NONE', ( -1.569783808284774200, 0.05618686558523977600, 0.8576654041047709900 ) ) ;
#294 = ADVANCED_FACE ( 'NONE', ( #577 ), #24, .T. ) ;
#295 = VECTOR ( 'NONE', #161, 1000.000000000000000 ) ;
#296 = CARTESIAN_POINT ( 'NONE', ( 0.6302161917152264700, 0.05618686558523977600, 0.8576654041047709900 ) ) ;
#297 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
#298 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION ( '', ( #61 ), #332 ) ;
#299 = ORIENTED_EDGE ( 'NONE', *, *, #505, .T. ) ;
#300 = LINE ( 'NONE', #478, #119 ) ;
#301 = ORIENTED_EDGE ( 'NONE', *, *, #523, .T. ) ;
#302 = PLANE ( 'NONE', #470 ) ;
#303 = COLOUR_RGB ( '',0.5372549019607842900, 0.3490196078431372400, 0.3372549019607843400 ) ;
#304 = VECTOR ( 'NONE', #276, 1000.000000000000000 ) ;
#305 = ORIENTED_EDGE ( 'NONE', *, *, #481, .T. ) ;
#306 = AXIS2_PLACEMENT_3D ( 'NONE', #91, #271, #716 ) ;
#307 = STYLED_ITEM ( 'NONE', ( #122 ), #34 ) ;
#308 = PRODUCT_CONTEXT ( 'NONE', #628, 'mechanical' ) ;
#309 = FACE_OUTER_BOUND ( 'NONE', #9, .T. ) ;
#310 = FACE_OUTER_BOUND ( 'NONE', #431, .T. ) ;
#311 = VERTEX_POINT ( 'NONE', #648 ) ;
#312 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
#313 = CARTESIAN_POINT ( 'NONE', ( 0.5838472000464882700, 0.5861868655852398000, 0.8576654041047709900 ) ) ;
#314 = CARTESIAN_POINT ( 'NONE', ( -1.719783808284775000, -0.09381313441476020500, 0.6176654041047668900 ) ) ;
#315 = LINE ( 'NONE', #318, #465 ) ;
#316 = VECTOR ( 'NONE', #388, 1000.000000000000000 ) ;
#317 = VERTEX_POINT ( 'NONE', #260 ) ;
#318 = CARTESIAN_POINT ( 'NONE', ( -1.719783808284775000, 0.05618686558523977600, 0.6176654041047668900 ) ) ;
#319 = UNCERTAINTY_MEASURE_WITH_UNIT (LENGTH_MEASURE( 1.000000000000000100E-005 ), #496, 'distance_accuracy_value', 'NONE');
#320 = EDGE_CURVE ( 'NONE', #321, #73, #659, .T. ) ;
#321 = VERTEX_POINT ( 'NONE', #83 ) ;
#322 = PLANE ( 'NONE', #63 ) ;
#323 = PRESENTATION_STYLE_ASSIGNMENT (( #603 ) ) ;
#324 = VERTEX_POINT ( 'NONE', #444 ) ;
#325 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION ( '', ( #206 ), #363 ) ;
#326 = SURFACE_STYLE_USAGE ( .BOTH. , #767 ) ;
#327 = EDGE_CURVE ( 'NONE', #149, #734, #11, .T. ) ;
#328 = LINE ( 'NONE', #730, #205 ) ;
#329 = VECTOR ( 'NONE', #282, 1000.000000000000000 ) ;
#330 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
#331 = CARTESIAN_POINT ( 'NONE', ( 0.03021619171522313400, -0.09381313441476020500, 0.5076654041047713500 ) ) ;
#332 =( GEOMETRIC_REPRESENTATION_CONTEXT ( 3 ) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT ( ( #54 ) ) GLOBAL_UNIT_ASSIGNED_CONTEXT ( ( #231, #675, #291 ) ) REPRESENTATION_CONTEXT ( 'NONE', 'WORKASPACE' ) );
#333 = ORIENTED_EDGE ( 'NONE', *, *, #586, .T. ) ;
#334 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
#335 =( NAMED_UNIT ( * ) SI_UNIT ( $, .STERADIAN. ) SOLID_ANGLE_UNIT ( ) );
#336 = SURFACE_STYLE_FILL_AREA ( #256 ) ;
#337 = VECTOR ( 'NONE', #591, 1000.000000000000000 ) ;
#338 = VERTEX_POINT ( 'NONE', #584 ) ;
#339 = ORIENTED_EDGE ( 'NONE', *, *, #647, .F. ) ;
#340 = SURFACE_STYLE_USAGE ( .BOTH. , #153 ) ;
#341 = LINE ( 'NONE', #560, #183 ) ;
#342 = EDGE_LOOP ( 'NONE', ( #240, #541, #788, #43 ) ) ;
#343 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
#344 = PRESENTATION_LAYER_ASSIGNMENT ( '', '', ( #262 ) ) ;
#345 = COLOUR_RGB ( '',0.5372549019607842900, 0.3490196078431372400, 0.3372549019607843400 ) ;
#346 = PRESENTATION_LAYER_ASSIGNMENT ( '', '', ( #307 ) ) ;
#347 = FILL_AREA_STYLE_COLOUR ( '', #345 ) ;
#348 = CARTESIAN_POINT ( 'NONE', ( 0.6302161917152264700, 0.05618686558523977600, 0.8576654041047709900 ) ) ;
#349 = CARTESIAN_POINT ( 'NONE', ( 0.03021619171522313400, -0.09381313441476020500, 0.5076654041047713500 ) ) ;
#350 = DIRECTION ( 'NONE', ( -0.08715574274765836000, 0.9961946980917455500, 0.0000000000000000000 ) ) ;
#351 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
#352 = VERTEX_POINT ( 'NONE', #203 ) ;
#353 = ADVANCED_FACE ( 'NONE', ( #512 ), #763, .T. ) ;
#354 = LINE ( 'NONE', #129, #536 ) ;
#355 = ADVANCED_BREP_SHAPE_REPRESENTATION ( 'MSMP package', ( #543, #395 ), #33 ) ;
#356 = LINE ( 'NONE', #525, #684 ) ;
#357 = EDGE_LOOP ( 'NONE', ( #366, #804, #514, #162 ) ) ;
#358 = CARTESIAN_POINT ( 'NONE', ( -1.556660508755886000, -0.09381313441476020500, -0.4423345958952280600 ) ) ;
#359 = CARTESIAN_POINT ( 'NONE', ( 0.6170928921863383400, -0.09381313441476020500, 0.5076654041047713500 ) ) ;
#360 = EDGE_CURVE ( 'NONE', #284, #621, #177, .T. ) ;
#361 = LINE ( 'NONE', #226, #686 ) ;
#362 = ORIENTED_EDGE ( 'NONE', *, *, #593, .F. ) ;
#363 =( GEOMETRIC_REPRESENTATION_CONTEXT ( 3 ) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT ( ( #41 ) ) GLOBAL_UNIT_ASSIGNED_CONTEXT ( ( #708, #263, #207 ) ) REPRESENTATION_CONTEXT ( 'NONE', 'WORKASPACE' ) );
#364 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
#365 = EDGE_CURVE ( 'NONE', #545, #443, #315, .T. ) ;
#366 = ORIENTED_EDGE ( 'NONE', *, *, #124, .T. ) ;
#367 =( NAMED_UNIT ( * ) PLANE_ANGLE_UNIT ( ) SI_UNIT ( $, .RADIAN. ) );
#368 = VERTEX_POINT ( 'NONE', #75 ) ;
#369 =( NAMED_UNIT ( * ) PLANE_ANGLE_UNIT ( ) SI_UNIT ( $, .RADIAN. ) );
#370 = VECTOR ( 'NONE', #761, 1000.000000000000000 ) ;
#371 = CARTESIAN_POINT ( 'NONE', ( 0.7802161917152238300, 0.05618686558523977600, -0.09233459589523193600 ) ) ;
#372 = VERTEX_POINT ( 'NONE', #515 ) ;
#373 = DIRECTION ( 'NONE', ( -0.08682659386424779200, 0.9924325091389669700, 0.08682659386424779200 ) ) ;
#374 = ORIENTED_EDGE ( 'NONE', *, *, #625, .F. ) ;
#375 = AXIS2_PLACEMENT_3D ( 'NONE', #803, #58, #620 ) ;
#376 = CARTESIAN_POINT ( 'NONE', ( 0.7802161917152238300, -0.09381313441476020500, -0.09233459589523193600 ) ) ;
#377 = CARTESIAN_POINT ( 'NONE', ( 0.7802161917152238300, 0.05618686558523977600, 0.5076654041047713500 ) ) ;
#378 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
#379 = COLOUR_RGB ( '',0.5372549019607842900, 0.3490196078431372400, 0.3372549019607843400 ) ;
#380 = PRESENTATION_STYLE_ASSIGNMENT (( #152 ) ) ;
#381 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION ( '', ( #262 ), #425 ) ;
#382 = FILL_AREA_STYLE ('',( #87 ) ) ;
#383 = PLANE ( 'NONE', #227 ) ;
#384 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION ( '', ( #307 ), #426 ) ;
#385 = ORIENTED_EDGE ( 'NONE', *, *, #688, .F. ) ;
#386 = UNCERTAINTY_MEASURE_WITH_UNIT (LENGTH_MEASURE( 1.000000000000000100E-005 ), #600, 'distance_accuracy_value', 'NONE');
#387 = FILL_AREA_STYLE_COLOUR ( '', #733 ) ;
#388 = DIRECTION ( 'NONE', ( 1.000000000000000000, 1.387778780781445700E-017, 0.0000000000000000000 ) ) ;
#389 = FILL_AREA_STYLE ('',( #535 ) ) ;
#390 = EDGE_LOOP ( 'NONE', ( #643, #305, #522, #249, #456, #657 ) ) ;
#391 = FILL_AREA_STYLE ('',( #595 ) ) ;
#392 = DIRECTION ( 'NONE', ( 0.08715574274765836000, 0.9961946980917455500, 0.0000000000000000000 ) ) ;
#393 = SURFACE_STYLE_FILL_AREA ( #382 ) ;
#394 = ORIENTED_EDGE ( 'NONE', *, *, #142, .T. ) ;
#395 = AXIS2_PLACEMENT_3D ( 'NONE', #255, #364, #748 ) ;
#396 = ADVANCED_FACE ( 'NONE', ( #639 ), #759, .F. ) ;
#397 = EDGE_LOOP ( 'NONE', ( #333, #517, #417, #219 ) ) ;
#398 = EDGE_CURVE ( 'NONE', #724, #731, #80, .T. ) ;
#399 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
#400 = DIRECTION ( 'NONE', ( 1.000000000000000000, -0.0000000000000000000, 0.0000000000000000000 ) ) ;
#401 = VECTOR ( 'NONE', #578, 1000.000000000000000 ) ;
#402 = SURFACE_STYLE_FILL_AREA ( #97 ) ;
#403 = CARTESIAN_POINT ( 'NONE', ( 0.6302161917152264700, 0.05618686558523977600, -0.4423345958952280600 ) ) ;
#404 = DIRECTION ( 'NONE', ( -1.000000000000000000, -0.0000000000000000000, -0.0000000000000000000 ) ) ;
#405 = SURFACE_STYLE_FILL_AREA ( #389 ) ;
#406 = ORIENTED_EDGE ( 'NONE', *, *, #442, .F. ) ;
#407 = EDGE_CURVE ( 'NONE', #311, #160, #587, .T. ) ;
#408 = DIRECTION ( 'NONE', ( 0.9961946980917455500, -0.08715574274765836000, 0.0000000000000000000 ) ) ;
#409 = FACE_OUTER_BOUND ( 'NONE', #287, .T. ) ;
#410 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
#411 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
#412 = VECTOR ( 'NONE', #654, 1000.000000000000100 ) ;
#413 = VECTOR ( 'NONE', #192, 1000.000000000000000 ) ;
#414 = ADVANCED_FACE ( 'NONE', ( #228 ), #322, .F. ) ;
#415 = DIRECTION ( 'NONE', ( 1.000000000000000000, 1.387778780781445700E-017, 0.0000000000000000000 ) ) ;
#416 = PLANE ( 'NONE', #725 ) ;
#417 = ORIENTED_EDGE ( 'NONE', *, *, #647, .T. ) ;
#418 = CARTESIAN_POINT ( 'NONE', ( -1.569783808284774200, 0.5861868655852398000, -0.4423345958952280600 ) ) ;
#419 = FACE_OUTER_BOUND ( 'NONE', #703, .T. ) ;
#420 = SURFACE_SIDE_STYLE ('',( #743 ) ) ;
#421 = VERTEX_POINT ( 'NONE', #634 ) ;
#422 = STYLED_ITEM ( 'NONE', ( #323 ), #220 ) ;
#423 = EDGE_CURVE ( 'NONE', #324, #665, #503, .T. ) ;
#424 = ORIENTED_EDGE ( 'NONE', *, *, #746, .F. ) ;
#425 =( GEOMETRIC_REPRESENTATION_CONTEXT ( 3 ) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT ( ( #775 ) ) GLOBAL_UNIT_ASSIGNED_CONTEXT ( ( #94, #534, #148 ) ) REPRESENTATION_CONTEXT ( 'NONE', 'WORKASPACE' ) );
#426 =( GEOMETRIC_REPRESENTATION_CONTEXT ( 3 ) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT ( ( #622 ) ) GLOBAL_UNIT_ASSIGNED_CONTEXT ( ( #459, #98, #538 ) ) REPRESENTATION_CONTEXT ( 'NONE', 'WORKASPACE' ) );
#427 = LINE ( 'NONE', #156, #769 ) ;
#428 =( NAMED_UNIT ( * ) SI_UNIT ( $, .STERADIAN. ) SOLID_ANGLE_UNIT ( ) );
#429 = VECTOR ( 'NONE', #415, 1000.000000000000000 ) ;
#430 = LINE ( 'NONE', #727, #776 ) ;
#431 = EDGE_LOOP ( 'NONE', ( #693, #507, #754, #86 ) ) ;
#432 = ORIENTED_EDGE ( 'NONE', *, *, #22, .F. ) ;
#433 = SURFACE_STYLE_USAGE ( .BOTH. , #72 ) ;
#434 = VECTOR ( 'NONE', #555, 1000.000000000000000 ) ;
#435 = VERTEX_POINT ( 'NONE', #642 ) ;
#436 = PRODUCT_DEFINITION ( 'δ֪', '', #491, #494 ) ;
#437 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
#438 = CARTESIAN_POINT ( 'NONE', ( -1.719783808284775000, -0.09381313441476020500, -0.2023345958952274500 ) ) ;
#439 = DIRECTION ( 'NONE', ( 0.08715574274765836000, -0.9961946980917455500, 0.0000000000000000000 ) ) ;
#440 = DIRECTION ( 'NONE', ( -0.9961946980917455500, -0.08715574274765836000, 0.0000000000000000000 ) ) ;
#441 = VERTEX_POINT ( 'NONE', #376 ) ;
#442 = EDGE_CURVE ( 'NONE', #443, #284, #479, .T. ) ;
#443 = VERTEX_POINT ( 'NONE', #200 ) ;
#444 = CARTESIAN_POINT ( 'NONE', ( 0.6302161917152264700, 0.05618686558523977600, -0.09233459589523193600 ) ) ;
#445 = UNCERTAINTY_MEASURE_WITH_UNIT (LENGTH_MEASURE( 1.000000000000000100E-005 ), #138, 'distance_accuracy_value', 'NONE');
#446 = CARTESIAN_POINT ( 'NONE', ( 0.6302161917152264700, 0.05618686558523977600, 0.8576654041047709900 ) ) ;
#447 = CARTESIAN_POINT ( 'NONE', ( -1.719783808284775000, -0.09381313441476020500, -0.2023345958952274500 ) ) ;
#448 = EDGE_CURVE ( 'NONE', #441, #724, #59, .T. ) ;
#449 = LINE ( 'NONE', #313, #413 ) ;
#450 =( NAMED_UNIT ( * ) SI_UNIT ( $, .STERADIAN. ) SOLID_ANGLE_UNIT ( ) );
#451 = CARTESIAN_POINT ( 'NONE', ( 0.6204156770925408600, -0.05583352914605126800, -0.4325340812725425600 ) ) ;
#452 = LINE ( 'NONE', #471, #615 ) ;
#453 = PLANE ( 'NONE', #572 ) ;
#454 = SURFACE_STYLE_USAGE ( .BOTH. , #29 ) ;
#455 = AXIS2_PLACEMENT_3D ( 'NONE', #349, #795, #410 ) ;
#456 = ORIENTED_EDGE ( 'NONE', *, *, #766, .F. ) ;
#457 = DIRECTION ( 'NONE', ( 0.9961946980917455500, -0.08715574274765836000, 0.0000000000000000000 ) ) ;
#458 = PLANE ( 'NONE', #742 ) ;
#459 =( LENGTH_UNIT ( ) NAMED_UNIT ( * ) SI_UNIT ( .MILLI., .METRE. ) );
#460 = LINE ( 'NONE', #53, #130 ) ;
#461 = ORIENTED_EDGE ( 'NONE', *, *, #481, .F. ) ;
#462 = CARTESIAN_POINT ( 'NONE', ( 0.6302161917152264700, 0.05618686558523976900, -0.09233459589523193600 ) ) ;
#463 = DIRECTION ( 'NONE', ( 0.08682659386424779200, -0.9924325091389669700, -0.08682659386424779200 ) ) ;
#464 = ADVANCED_FACE ( 'NONE', ( #218 ), #134, .T. ) ;
#465 = VECTOR ( 'NONE', #760, 1000.000000000000000 ) ;
#466 = PLANE ( 'NONE', #13 ) ;
#467 = COLOUR_RGB ( '',0.7921568627450980000, 0.8196078431372548800, 0.9333333333333333500 ) ;
#468 = UNCERTAINTY_MEASURE_WITH_UNIT (LENGTH_MEASURE( 1.000000000000000100E-005 ), #174, 'distance_accuracy_value', 'NONE');
#469 = ORIENTED_EDGE ( 'NONE', *, *, #4, .F. ) ;
#470 = AXIS2_PLACEMENT_3D ( 'NONE', #180, #783, #617 ) ;
#471 = CARTESIAN_POINT ( 'NONE', ( -1.719783808284775000, 0.05618686558523977600, -0.2023345958952274500 ) ) ;
#472 = VERTEX_POINT ( 'NONE', #709 ) ;
#473 = EDGE_CURVE ( 'NONE', #665, #571, #356, .T. ) ;
#474 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.08715574274765836000, -0.9961946980917455500 ) ) ;
#475 = DIRECTION ( 'NONE', ( 0.08715574274765836000, 0.9961946980917455500, 0.0000000000000000000 ) ) ;
#476 = STYLED_ITEM ( 'NONE', ( #380 ), #556 ) ;
#477 = PLANE ( 'NONE', #455 ) ;
#478 = CARTESIAN_POINT ( 'NONE', ( 0.6302161917152264700, 0.5861868655852398000, -0.3959656042264898500 ) ) ;
#479 = LINE ( 'NONE', #702, #370 ) ;
#480 = VECTOR ( 'NONE', #723, 1000.000000000000000 ) ;
#481 = EDGE_CURVE ( 'NONE', #368, #324, #36, .T. ) ;
#482 = CARTESIAN_POINT ( 'NONE', ( 0.6170928921863383400, -0.09381313441476023300, -0.4292112963663399800 ) ) ;
#483 = UNCERTAINTY_MEASURE_WITH_UNIT (LENGTH_MEASURE( 1.000000000000000100E-005 ), #10, 'distance_accuracy_value', 'NONE');
#484 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -1.000000000000000000, -0.0000000000000000000 ) ) ;
#485 = ADVANCED_FACE ( 'NONE', ( #178 ), #383, .T. ) ;
#486 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
#487 = LINE ( 'NONE', #737, #656 ) ;
#488 = EDGE_LOOP ( 'NONE', ( #55, #16, #710, #301 ) ) ;
#489 = FILL_AREA_STYLE ('',( #117 ) ) ;
#490 = VECTOR ( 'NONE', #209, 1000.000000000000100 ) ;
#491 = PRODUCT_DEFINITION_FORMATION_WITH_SPECIFIED_SOURCE ( 'ÈκÎ', '', #197, .NOT_KNOWN. ) ;
#492 = VECTOR ( 'NONE', #771, 1000.000000000000100 ) ;
#493 = ORIENTED_EDGE ( 'NONE', *, *, #746, .T. ) ;
#494 = PRODUCT_DEFINITION_CONTEXT ( 'detailed design', #690, 'design' ) ;
#495 =( GEOMETRIC_REPRESENTATION_CONTEXT ( 3 ) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT ( ( #104 ) ) GLOBAL_UNIT_ASSIGNED_CONTEXT ( ( #581, #132, #511 ) ) REPRESENTATION_CONTEXT ( 'NONE', 'WORKASPACE' ) );
#496 =( LENGTH_UNIT ( ) NAMED_UNIT ( * ) SI_UNIT ( .MILLI., .METRE. ) );
#497 = ORIENTED_EDGE ( 'NONE', *, *, #360, .T. ) ;
#498 = EDGE_CURVE ( 'NONE', #724, #607, #513, .T. ) ;
#499 = FACE_OUTER_BOUND ( 'NONE', #102, .T. ) ;
#500 = VERTEX_POINT ( 'NONE', #649 ) ;
#501 = AXIS2_PLACEMENT_3D ( 'NONE', #524, #699, #14 ) ;
#502 =( LENGTH_UNIT ( ) NAMED_UNIT ( * ) SI_UNIT ( .MILLI., .METRE. ) );
#503 = LINE ( 'NONE', #15, #5 ) ;
#504 = LINE ( 'NONE', #462, #145 ) ;
#505 = EDGE_CURVE ( 'NONE', #621, #311, #194, .T. ) ;
#506 = DIRECTION ( 'NONE', ( -1.000000000000000000, -0.0000000000000000000, -0.0000000000000000000 ) ) ;
#507 = ORIENTED_EDGE ( 'NONE', *, *, #68, .T. ) ;
#508 = STYLED_ITEM ( 'NONE', ( #597 ), #781 ) ;
#509 = FILL_AREA_STYLE ('',( #580 ) ) ;
#510 = VECTOR ( 'NONE', #506, 1000.000000000000000 ) ;
#511 =( NAMED_UNIT ( * ) SI_UNIT ( $, .STERADIAN. ) SOLID_ANGLE_UNIT ( ) );
#512 = FACE_OUTER_BOUND ( 'NONE', #77, .T. ) ;
#513 = LINE ( 'NONE', #210, #337 ) ;
#514 = ORIENTED_EDGE ( 'NONE', *, *, #567, .T. ) ;
#515 = CARTESIAN_POINT ( 'NONE', ( 0.5838472000464882700, 0.5861868655852398000, -0.3959656042264898500 ) ) ;
#516 = CARTESIAN_POINT ( 'NONE', ( 0.03021619171522313400, -0.09381313441476020500, -0.09233459589523193600 ) ) ;
#517 = ORIENTED_EDGE ( 'NONE', *, *, #667, .F. ) ;
#518 = PRESENTATION_LAYER_ASSIGNMENT ( '', '', ( #422 ) ) ;
#519 = CARTESIAN_POINT ( 'NONE', ( -1.569783808284774200, 0.05618686558523977600, -0.4423345958952280600 ) ) ;
#520 =( NAMED_UNIT ( * ) PLANE_ANGLE_UNIT ( ) SI_UNIT ( $, .RADIAN. ) );
#521 = VECTOR ( 'NONE', #343, 1000.000000000000000 ) ;
#522 = ORIENTED_EDGE ( 'NONE', *, *, #423, .T. ) ;
#523 = EDGE_CURVE ( 'NONE', #731, #665, #12, .T. ) ;
#524 = CARTESIAN_POINT ( 'NONE', ( -1.719783808284775000, -0.09381313441476020500, -0.2023345958952274500 ) ) ;
#525 = CARTESIAN_POINT ( 'NONE', ( 0.6302161917152264700, 0.05618686558523977600, -0.4423345958952280600 ) ) ;
#526 = VECTOR ( 'NONE', #792, 1000.000000000000000 ) ;
#527 = PRESENTATION_LAYER_ASSIGNMENT ( '', '', ( #476 ) ) ;
#528 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
#529 = ORIENTED_EDGE ( 'NONE', *, *, #407, .T. ) ;
#530 =( LENGTH_UNIT ( ) NAMED_UNIT ( * ) SI_UNIT ( .MILLI., .METRE. ) );
#531 = EDGE_CURVE ( 'NONE', #253, #435, #159, .T. ) ;
#532 = DIRECTION ( 'NONE', ( -1.000000000000000000, -0.0000000000000000000, -0.0000000000000000000 ) ) ;
#533 = ORIENTED_EDGE ( 'NONE', *, *, #554, .T. ) ;
#534 =( NAMED_UNIT ( * ) PLANE_ANGLE_UNIT ( ) SI_UNIT ( $, .RADIAN. ) );
#535 = FILL_AREA_STYLE_COLOUR ( '', #285 ) ;
#536 = VECTOR ( 'NONE', #576, 1000.000000000000100 ) ;
#537 = SURFACE_SIDE_STYLE ('',( #402 ) ) ;
#538 =( NAMED_UNIT ( * ) SI_UNIT ( $, .STERADIAN. ) SOLID_ANGLE_UNIT ( ) );
#539 = VECTOR ( 'NONE', #25, 1000.000000000000000 ) ;
#540 = ADVANCED_FACE ( 'NONE', ( #807 ), #213, .T. ) ;
#541 = ORIENTED_EDGE ( 'NONE', *, *, #606, .F. ) ;
#542 = CARTESIAN_POINT ( 'NONE', ( -1.569783808284774200, 0.05618686558523977600, -0.4423345958952280600 ) ) ;
#543 = MANIFOLD_SOLID_BREP ( 'Ïû³ýÌØÕ÷1', #618 ) ;
#544 = PRODUCT_RELATED_PRODUCT_CATEGORY ( 'part', '', ( #197 ) ) ;
#545 = VERTEX_POINT ( 'NONE', #655 ) ;
#546 = ORIENTED_EDGE ( 'NONE', *, *, #185, .F. ) ;
#547 = EDGE_CURVE ( 'NONE', #565, #621, #243, .T. ) ;
#548 = CARTESIAN_POINT ( 'NONE', ( -1.569783808284774200, 0.05618686558523977600, -0.4423345958952280600 ) ) ;
#549 = CARTESIAN_POINT ( 'NONE', ( -0.4197838082847724100, -0.09381313441475436200, -0.2023345958952274500 ) ) ;
#550 = CARTESIAN_POINT ( 'NONE', ( 0.7802161917152238300, 0.05618686558523977600, 0.5076654041047713500 ) ) ;
#551 = STYLED_ITEM ( 'NONE', ( #135 ), #396 ) ;
#552 = FILL_AREA_STYLE ('',( #347 ) ) ;
#553 = ORIENTED_EDGE ( 'NONE', *, *, #64, .T. ) ;
#554 = EDGE_CURVE ( 'NONE', #188, #734, #136, .T. ) ;
#555 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -1.000000000000000000, -0.0000000000000000000 ) ) ;
#556 = ADVANCED_FACE ( 'NONE', ( #728 ), #458, .T. ) ;
#557 = CARTESIAN_POINT ( 'NONE', ( 0.7802161917152238300, -0.09381313441476020500, 0.5076654041047713500 ) ) ;
#558 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.9961946980917455500, 0.08715574274765836000 ) ) ;
#559 = VERTEX_POINT ( 'NONE', #777 ) ;
#560 = CARTESIAN_POINT ( 'NONE', ( 0.7802161917152238300, 0.05618686558523977600, -0.09233459589523193600 ) ) ;
#561 = AXIS2_PLACEMENT_3D ( 'NONE', #296, #736, #350 ) ;
#562 = ORIENTED_EDGE ( 'NONE', *, *, #789, .F. ) ;
#563 = ORIENTED_EDGE ( 'NONE', *, *, #688, .T. ) ;
#564 = CARTESIAN_POINT ( 'NONE', ( -1.523414816616035900, 0.5861868655852398000, 0.8112964124360327900 ) ) ;
#565 = VERTEX_POINT ( 'NONE', #99 ) ;
#566 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION ( '', ( #422 ), #605 ) ;
#567 = EDGE_CURVE ( 'NONE', #179, #317, #673, .T. ) ;
#568 = VECTOR ( 'NONE', #696, 1000.000000000000000 ) ;
#569 = COLOUR_RGB ( '',0.5372549019607842900, 0.3490196078431372400, 0.3372549019607843400 ) ;
#570 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION ( '', ( #476 ), #609 ) ;
#571 = VERTEX_POINT ( 'NONE', #542 ) ;
#572 = AXIS2_PLACEMENT_3D ( 'NONE', #106, #351, #800 ) ;
#573 = SURFACE_SIDE_STYLE ('',( #794 ) ) ;
#574 = ORIENTED_EDGE ( 'NONE', *, *, #789, .T. ) ;
#575 = EDGE_LOOP ( 'NONE', ( #139, #275, #211, #339 ) ) ;
#576 = DIRECTION ( 'NONE', ( -0.08682659386424779200, -0.9924325091389669700, -0.08682659386424779200 ) ) ;
#577 = FACE_OUTER_BOUND ( 'NONE', #579, .T. ) ;
#578 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
#579 = EDGE_LOOP ( 'NONE', ( #246, #280, #719, #700, #546, #121 ) ) ;
#580 = FILL_AREA_STYLE_COLOUR ( '', #251 ) ;
#581 =( LENGTH_UNIT ( ) NAMED_UNIT ( * ) SI_UNIT ( .MILLI., .METRE. ) );
#582 = ORIENTED_EDGE ( 'NONE', *, *, #208, .F. ) ;
#583 = STYLED_ITEM ( 'NONE', ( #726 ), #414 ) ;
#584 = CARTESIAN_POINT ( 'NONE', ( -1.523414816616035900, 0.5861868655852398000, -0.3959656042264898500 ) ) ;
#585 = SURFACE_STYLE_USAGE ( .BOTH. , #204 ) ;
#586 = EDGE_CURVE ( 'NONE', #443, #565, #26, .T. ) ;
#587 = LINE ( 'NONE', #254, #568 ) ;
#588 = ORIENTED_EDGE ( 'NONE', *, *, #208, .T. ) ;
#589 = AXIS2_PLACEMENT_3D ( 'NONE', #519, #78, #439 ) ;
#590 = CARTESIAN_POINT ( 'NONE', ( -1.556660508755886000, -0.09381313441476020500, -0.2023345958952274500 ) ) ;
#591 = DIRECTION ( 'NONE', ( -1.000000000000000000, -1.387778780781445700E-017, 0.0000000000000000000 ) ) ;
#592 = SURFACE_STYLE_FILL_AREA ( #212 ) ;
#593 = EDGE_CURVE ( 'NONE', #338, #179, #6, .T. ) ;
#594 = SURFACE_SIDE_STYLE ('',( #592 ) ) ;
#595 = FILL_AREA_STYLE_COLOUR ( '', #467 ) ;
#596 = VECTOR ( 'NONE', #717, 1000.000000000000000 ) ;
#597 = PRESENTATION_STYLE_ASSIGNMENT (( #433 ) ) ;
#598 = ORIENTED_EDGE ( 'NONE', *, *, #676, .T. ) ;
#599 = PRESENTATION_LAYER_ASSIGNMENT ( '', '', ( #508 ) ) ;
#600 =( LENGTH_UNIT ( ) NAMED_UNIT ( * ) SI_UNIT ( .MILLI., .METRE. ) );
#601 = LINE ( 'NONE', #671, #257 ) ;
#602 = ORIENTED_EDGE ( 'NONE', *, *, #473, .T. ) ;
#603 = SURFACE_STYLE_USAGE ( .BOTH. , #118 ) ;
#604 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.08715574274765836000, 0.9961946980917455500 ) ) ;
#605 =( GEOMETRIC_REPRESENTATION_CONTEXT ( 3 ) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT ( ( #319 ) ) GLOBAL_UNIT_ASSIGNED_CONTEXT ( ( #496, #100, #335 ) ) REPRESENTATION_CONTEXT ( 'NONE', 'WORKASPACE' ) );
#606 = EDGE_CURVE ( 'NONE', #324, #724, #504, .T. ) ;
#607 = VERTEX_POINT ( 'NONE', #39 ) ;
#608 = CARTESIAN_POINT ( 'NONE', ( 0.7802161917152238300, -0.09381313441476020500, -0.09233459589523193600 ) ) ;
#609 =( GEOMETRIC_REPRESENTATION_CONTEXT ( 3 ) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT ( ( #386 ) ) GLOBAL_UNIT_ASSIGNED_CONTEXT ( ( #600, #217, #658 ) ) REPRESENTATION_CONTEXT ( 'NONE', 'WORKASPACE' ) );
#610 = LINE ( 'NONE', #721, #110 ) ;
#611 = SURFACE_STYLE_USAGE ( .BOTH. , #169 ) ;
#612 = CARTESIAN_POINT ( 'NONE', ( 0.6302161917152264700, 0.05618686558523977600, 0.8576654041047709900 ) ) ;
#613 = VECTOR ( 'NONE', #171, 1000.000000000000000 ) ;
#614 = CARTESIAN_POINT ( 'NONE', ( 0.03021619171521966500, -0.09381313441475436200, 0.5076654041047713500 ) ) ;
#615 = VECTOR ( 'NONE', #786, 1000.000000000000000 ) ;
#616 = ADVANCED_FACE ( 'NONE', ( #309 ), #466, .T. ) ;
#617 = DIRECTION ( 'NONE', ( -0.08715574274765836000, -0.9961946980917455500, 0.0000000000000000000 ) ) ;
#618 = CLOSED_SHELL ( 'NONE', ( #414, #34, #220, #157, #173, #616, #234, #679, #540, #353, #294, #464, #52, #485, #799, #735, #115, #396, #722, #278, #556, #781 ) ) ;
#619 = LINE ( 'NONE', #798, #434 ) ;
#620 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, -0.0000000000000000000 ) ) ;
#621 = VERTEX_POINT ( 'NONE', #782 ) ;
#622 = UNCERTAINTY_MEASURE_WITH_UNIT (LENGTH_MEASURE( 1.000000000000000100E-005 ), #459, 'distance_accuracy_value', 'NONE');
#623 = AXIS2_PLACEMENT_3D ( 'NONE', #371, #756, #312 ) ;
#624 = ORIENTED_EDGE ( 'NONE', *, *, #327, .T. ) ;
#625 = EDGE_CURVE ( 'NONE', #607, #352, #631, .T. ) ;
#626 = ORIENTED_EDGE ( 'NONE', *, *, #28, .F. ) ;
#627 = PRESENTATION_LAYER_ASSIGNMENT ( '', '', ( #551 ) ) ;
#628 = APPLICATION_CONTEXT ( 'automotive_design' ) ;
#629 = EDGE_CURVE ( 'NONE', #621, #421, #652, .T. ) ;
#630 = PRESENTATION_STYLE_ASSIGNMENT (( #125 ) ) ;
#631 = LINE ( 'NONE', #664, #295 ) ;
#632 = ORIENTED_EDGE ( 'NONE', *, *, #676, .F. ) ;
#633 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION ( '', ( #508 ), #682 ) ;
#634 = CARTESIAN_POINT ( 'NONE', ( -1.556660508755886000, -0.09381313441476023300, 0.8445421045758828600 ) ) ;
#635 = VECTOR ( 'NONE', #232, 1000.000000000000000 ) ;
#636 = ORIENTED_EDGE ( 'NONE', *, *, #547, .F. ) ;
#637 = PLANE ( 'NONE', #805 ) ;
#638 = SURFACE_SIDE_STYLE ('',( #336 ) ) ;
#639 = FACE_OUTER_BOUND ( 'NONE', #644, .T. ) ;
#640 = VECTOR ( 'NONE', #532, 1000.000000000000000 ) ;
#641 = CARTESIAN_POINT ( 'NONE', ( -1.419783808284780200, -0.09381313441476020500, 0.6176654041047668900 ) ) ;
#642 = CARTESIAN_POINT ( 'NONE', ( -1.719783808284775000, -0.09381313441476020500, -0.2023345958952274500 ) ) ;
#643 = ORIENTED_EDGE ( 'NONE', *, *, #265, .T. ) ;
#644 = EDGE_LOOP ( 'NONE', ( #739, #40, #582, #624 ) ) ;
#645 = CARTESIAN_POINT ( 'NONE', ( 0.6136307054306795500, -0.1333861101138683600, 0.8410799178202241800 ) ) ;
#646 = FILL_AREA_STYLE ('',( #706 ) ) ;
#647 = EDGE_CURVE ( 'NONE', #559, #545, #452, .T. ) ;
#648 = CARTESIAN_POINT ( 'NONE', ( -0.4197838082847689100, -0.09381313441476020500, 0.6176654041047668900 ) ) ;
#649 = CARTESIAN_POINT ( 'NONE', ( -1.569783808284774200, 0.05618686558523977600, 0.8576654041047709900 ) ) ;
#650 = AXIS2_PLACEMENT_3D ( 'NONE', #235, #113, #558 ) ;
#651 = FACE_OUTER_BOUND ( 'NONE', #165, .T. ) ;
#652 = LINE ( 'NONE', #283, #796 ) ;
#653 = PLANE ( 'NONE', #244 ) ;
#654 = DIRECTION ( 'NONE', ( -0.08682659386424779200, 0.9924325091389669700, -0.08682659386424779200 ) ) ;
#655 = CARTESIAN_POINT ( 'NONE', ( -1.719783808284775000, 0.05618686558523977600, -0.2023345958952274500 ) ) ;
#656 = VECTOR ( 'NONE', #297, 1000.000000000000000 ) ;
#657 = ORIENTED_EDGE ( 'NONE', *, *, #229, .T. ) ;
#658 =( NAMED_UNIT ( * ) SI_UNIT ( $, .STERADIAN. ) SOLID_ANGLE_UNIT ( ) );
#659 = LINE ( 'NONE', #645, #490 ) ;
#660 = DIRECTION ( 'NONE', ( -0.08715574274765836000, -0.9961946980917455500, 5.832412861803903500E-018 ) ) ;
#661 = SURFACE_STYLE_FILL_AREA ( #391 ) ;
#662 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
#663 = EDGE_LOOP ( 'NONE', ( #747, #669, #233, #801 ) ) ;
#664 = CARTESIAN_POINT ( 'NONE', ( 0.1802161917152204900, -0.09381313441475436200, -0.09233459589523193600 ) ) ;
#665 = VERTEX_POINT ( 'NONE', #163 ) ;
#666 = PRESENTATION_LAYER_ASSIGNMENT ( '', '', ( #583 ) ) ;
#667 = EDGE_CURVE ( 'NONE', #559, #565, #272, .T. ) ;
#668 = COLOUR_RGB ( '',0.5372549019607842900, 0.3490196078431372400, 0.3372549019607843400 ) ;
#669 = ORIENTED_EDGE ( 'NONE', *, *, #547, .T. ) ;
#670 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
#671 = CARTESIAN_POINT ( 'NONE', ( 0.6302161917152264700, 0.05618686558523977600, 0.5076654041047713500 ) ) ;
#672 = VECTOR ( 'NONE', #670, 1000.000000000000000 ) ;
#673 = LINE ( 'NONE', #35, #170 ) ;
#674 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION ( '', ( #551 ), #495 ) ;
#675 =( NAMED_UNIT ( * ) PLANE_ANGLE_UNIT ( ) SI_UNIT ( $, .RADIAN. ) );
#676 = EDGE_CURVE ( 'NONE', #368, #149, #430, .T. ) ;
#677 = PLANE ( 'NONE', #561 ) ;
#678 = CARTESIAN_POINT ( 'NONE', ( -1.719783808284775000, 0.05618686558523977600, -0.2023345958952274500 ) ) ;
#679 = ADVANCED_FACE ( 'NONE', ( #651 ), #790, .T. ) ;
#680 = ORIENTED_EDGE ( 'NONE', *, *, #48, .F. ) ;
#681 =( NAMED_UNIT ( * ) PLANE_ANGLE_UNIT ( ) SI_UNIT ( $, .RADIAN. ) );
#682 =( GEOMETRIC_REPRESENTATION_CONTEXT ( 3 ) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT ( ( #483 ) ) GLOBAL_UNIT_ASSIGNED_CONTEXT ( ( #10, #369, #755 ) ) REPRESENTATION_CONTEXT ( 'NONE', 'WORKASPACE' ) );
#683 =( NAMED_UNIT ( * ) SI_UNIT ( $, .STERADIAN. ) SOLID_ANGLE_UNIT ( ) );
#684 = VECTOR ( 'NONE', #768, 1000.000000000000000 ) ;
#685 = VERTEX_POINT ( 'NONE', #225 ) ;
#686 = VECTOR ( 'NONE', #57, 1000.000000000000000 ) ;
#687 = ORIENTED_EDGE ( 'NONE', *, *, #729, .F. ) ;
#688 = EDGE_CURVE ( 'NONE', #731, #472, #168, .T. ) ;
#689 = LINE ( 'NONE', #289, #526 ) ;
#690 = APPLICATION_CONTEXT ( 'automotive_design' ) ;
#691 = STYLED_ITEM ( 'NONE', ( #279 ), #157 ) ;
#692 = APPLICATION_PROTOCOL_DEFINITION ( 'draft international standard', 'automotive_design', 1998, #628 ) ;
#693 = ORIENTED_EDGE ( 'NONE', *, *, #751, .F. ) ;
#694 = ORIENTED_EDGE ( 'NONE', *, *, #567, .F. ) ;
#695 = CARTESIAN_POINT ( 'NONE', ( 0.03021619171522313400, 0.05618686558523977600, -0.09233459589523193600 ) ) ;
#696 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -5.854691731421723900E-018, -1.000000000000000000 ) ) ;
#697 = VECTOR ( 'NONE', #264, 1000.000000000000000 ) ;
#698 = ORIENTED_EDGE ( 'NONE', *, *, #167, .F. ) ;
#699 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
#700 = ORIENTED_EDGE ( 'NONE', *, *, #360, .F. ) ;
#701 = FILL_AREA_STYLE_COLOUR ( '', #303 ) ;
#702 = CARTESIAN_POINT ( 'NONE', ( -1.719783808284775000, 0.05618686558523977600, 0.6176654041047668900 ) ) ;
#703 = EDGE_LOOP ( 'NONE', ( #636, #60, #96, #497 ) ) ;
#704 = UNCERTAINTY_MEASURE_WITH_UNIT (LENGTH_MEASURE( 1.000000000000000100E-005 ), #7, 'distance_accuracy_value', 'NONE');
#705 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION ( '', ( #583 ), #745 ) ;
#706 = FILL_AREA_STYLE_COLOUR ( '', #379 ) ;
#707 = EDGE_CURVE ( 'NONE', #253, #559, #328, .T. ) ;
#708 =( LENGTH_UNIT ( ) NAMED_UNIT ( * ) SI_UNIT ( .MILLI., .METRE. ) );
#709 = CARTESIAN_POINT ( 'NONE', ( -1.556660508755886000, -0.09381313441476023300, -0.4292112963663399800 ) ) ;
#710 = ORIENTED_EDGE ( 'NONE', *, *, #398, .T. ) ;
#711 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, -0.0000000000000000000 ) ) ;
#712 = CARTESIAN_POINT ( 'NONE', ( -1.419783808284783800, -0.09381313441475439000, 0.6176654041047668900 ) ) ;
#713 = EDGE_CURVE ( 'NONE', #565, #500, #689, .T. ) ;
#714 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
#715 = ORIENTED_EDGE ( 'NONE', *, *, #629, .F. ) ;
#716 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -0.9961946980917455500, 0.08715574274765836000 ) ) ;
#717 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
#718 = DIRECTION ( 'NONE', ( 0.08715574274765836000, -0.9961946980917455500, 5.832412861803903500E-018 ) ) ;
#719 = ORIENTED_EDGE ( 'NONE', *, *, #505, .F. ) ;
#720 = CARTESIAN_POINT ( 'NONE', ( 0.03021619171522313400, 0.05618686558523977600, 0.5076654041047713500 ) ) ;
#721 = CARTESIAN_POINT ( 'NONE', ( 0.6302161917152264700, 0.05618686558523977600, -0.4423345958952280600 ) ) ;
#722 = ADVANCED_FACE ( 'NONE', ( #47 ), #477, .F. ) ;
#723 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
#724 = VERTEX_POINT ( 'NONE', #49 ) ;
#725 = AXIS2_PLACEMENT_3D ( 'NONE', #44, #400, #787 ) ;
#726 = PRESENTATION_STYLE_ASSIGNMENT (( #585 ) ) ;
#727 = CARTESIAN_POINT ( 'NONE', ( 0.03021619171522313400, 0.05618686558523977600, 0.5076654041047713500 ) ) ;
#728 = FACE_OUTER_BOUND ( 'NONE', #230, .T. ) ;
#729 = EDGE_CURVE ( 'NONE', #734, #441, #126, .T. ) ;
#730 = CARTESIAN_POINT ( 'NONE', ( -1.569783808284774200, 0.05618686558523977600, -0.2023345958952274500 ) ) ;
#731 = VERTEX_POINT ( 'NONE', #482 ) ;
#732 = AXIS2_PLACEMENT_3D ( 'NONE', #348, #408, #475 ) ;
#733 = COLOUR_RGB ( '',0.5372549019607842900, 0.3490196078431372400, 0.3372549019607843400 ) ;
#734 = VERTEX_POINT ( 'NONE', #557 ) ;
#735 = ADVANCED_FACE ( 'NONE', ( #269 ), #677, .T. ) ;
#736 = DIRECTION ( 'NONE', ( 0.9961946980917455500, 0.08715574274765836000, 0.0000000000000000000 ) ) ;
#737 = CARTESIAN_POINT ( 'NONE', ( -1.569783808284774200, 0.05618686558523977600, -0.4423345958952280600 ) ) ;
#738 = LINE ( 'NONE', #267, #237 ) ;
#739 = ORIENTED_EDGE ( 'NONE', *, *, #729, .T. ) ;
#740 = FILL_AREA_STYLE ('',( #791 ) ) ;
#741 = ORIENTED_EDGE ( 'NONE', *, *, #124, .F. ) ;
#742 = AXIS2_PLACEMENT_3D ( 'NONE', #720, #334, #780 ) ;
#743 = SURFACE_STYLE_FILL_AREA ( #740 ) ;
#744 = ORIENTED_EDGE ( 'NONE', *, *, #365, .F. ) ;
#745 =( GEOMETRIC_REPRESENTATION_CONTEXT ( 3 ) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT ( ( #445 ) ) GLOBAL_UNIT_ASSIGNED_CONTEXT ( ( #138, #520, #79 ) ) REPRESENTATION_CONTEXT ( 'NONE', 'WORKASPACE' ) );
#746 = EDGE_CURVE ( 'NONE', #352, #188, #292, .T. ) ;
#747 = ORIENTED_EDGE ( 'NONE', *, *, #713, .F. ) ;
#748 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
#749 = PRESENTATION_STYLE_ASSIGNMENT (( #146 ) ) ;
#750 =( NAMED_UNIT ( * ) SI_UNIT ( $, .STERADIAN. ) SOLID_ANGLE_UNIT ( ) );
#751 = EDGE_CURVE ( 'NONE', #421, #500, #774, .T. ) ;
#752 = FACE_OUTER_BOUND ( 'NONE', #575, .T. ) ;
#753 = VECTOR ( 'NONE', #373, 1000.000000000000100 ) ;
#754 = ORIENTED_EDGE ( 'NONE', *, *, #320, .T. ) ;
#755 =( NAMED_UNIT ( * ) SI_UNIT ( $, .STERADIAN. ) SOLID_ANGLE_UNIT ( ) );
#756 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
#757 = FACE_OUTER_BOUND ( 'NONE', #488, .T. ) ;
#758 = ORIENTED_EDGE ( 'NONE', *, *, #667, .T. ) ;
#759 = PLANE ( 'NONE', #623 ) ;
#760 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
#761 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -1.000000000000000000, -0.0000000000000000000 ) ) ;
#762 = STYLED_ITEM ( 'NONE', ( #749 ), #278 ) ;
#763 = PLANE ( 'NONE', #589 ) ;
#764 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
#765 = ORIENTED_EDGE ( 'NONE', *, *, #167, .T. ) ;
#766 = EDGE_CURVE ( 'NONE', #317, #372, #449, .T. ) ;
#767 = SURFACE_SIDE_STYLE ('',( #393 ) ) ;
#768 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
#769 = VECTOR ( 'NONE', #50, 1000.000000000000100 ) ;
#770 = CARTESIAN_POINT ( 'NONE', ( 0.03021619171522313400, 0.05618686558523977600, 0.5076654041047713500 ) ) ;
#771 = DIRECTION ( 'NONE', ( 0.08682659386424779200, 0.9924325091389669700, -0.08682659386424779200 ) ) ;
#772 = VECTOR ( 'NONE', #147, 1000.000000000000100 ) ;
#773 = PRESENTATION_LAYER_ASSIGNMENT ( '', '', ( #691 ) ) ;
#774 = LINE ( 'NONE', #56, #753 ) ;
#775 = UNCERTAINTY_MEASURE_WITH_UNIT (LENGTH_MEASURE( 1.000000000000000100E-005 ), #94, 'distance_accuracy_value', 'NONE');
#776 = VECTOR ( 'NONE', #107, 1000.000000000000000 ) ;
#777 = CARTESIAN_POINT ( 'NONE', ( -1.569783808284774200, 0.05618686558523977600, -0.2023345958952274500 ) ) ;
#778 = LINE ( 'NONE', #293, #635 ) ;
#779 = EDGE_LOOP ( 'NONE', ( #424, #374, #808, #1, #687, #69 ) ) ;
#780 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
#781 = ADVANCED_FACE ( 'NONE', ( #93 ), #637, .F. ) ;
#782 = CARTESIAN_POINT ( 'NONE', ( -1.556660508755886000, -0.09381313441476020500, 0.6176654041047668900 ) ) ;
#783 = DIRECTION ( 'NONE', ( -0.9961946980917455500, 0.08715574274765836000, 0.0000000000000000000 ) ) ;
#784 = PRESENTATION_STYLE_ASSIGNMENT (( #454 ) ) ;
#785 = ORIENTED_EDGE ( 'NONE', *, *, #523, .F. ) ;
#786 = DIRECTION ( 'NONE', ( -1.000000000000000000, -0.0000000000000000000, -0.0000000000000000000 ) ) ;
#787 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
#788 = ORIENTED_EDGE ( 'NONE', *, *, #797, .F. ) ;
#789 = EDGE_CURVE ( 'NONE', #338, #571, #354, .T. ) ;
#790 = PLANE ( 'NONE', #103 ) ;
#791 = FILL_AREA_STYLE_COLOUR ( '', #101 ) ;
#792 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
#793 = DIRECTION ( 'NONE', ( 0.08715574274765836000, 0.9961946980917455500, -5.832412861803903500E-018 ) ) ;
#794 = SURFACE_STYLE_FILL_AREA ( #187 ) ;
#795 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
#796 = VECTOR ( 'NONE', #399, 1000.000000000000000 ) ;
#797 = EDGE_CURVE ( 'NONE', #685, #324, #190, .T. ) ;
#798 = CARTESIAN_POINT ( 'NONE', ( 0.7802161917152238300, 0.05618686558523977600, -0.09233459589523193600 ) ) ;
#799 = ADVANCED_FACE ( 'NONE', ( #176 ), #8, .T. ) ;
#800 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, -0.0000000000000000000 ) ) ;
#801 = ORIENTED_EDGE ( 'NONE', *, *, #751, .T. ) ;
#802 = VECTOR ( 'NONE', #484, 1000.000000000000000 ) ;
#803 = CARTESIAN_POINT ( 'NONE', ( -1.419783808284780200, 0.05618686558523977600, 0.6176654041047668900 ) ) ;
#804 = ORIENTED_EDGE ( 'NONE', *, *, #593, .T. ) ;
#805 = AXIS2_PLACEMENT_3D ( 'NONE', #695, #252, #189 ) ;
#806 = VECTOR ( 'NONE', #199, 1000.000000000000000 ) ;
#807 = FACE_OUTER_BOUND ( 'NONE', #198, .T. ) ;
#808 = ORIENTED_EDGE ( 'NONE', *, *, #498, .F. ) ;
ENDSEC;
END-ISO-10303-21;

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('STEP AP214'),'1');
FILE_NAME('CAP_YX_10X20_RUB','2024-01-10T03:53:25',(''),(''),'','','');
FILE_NAME('CAP_YX_10X20_RUB','2023-11-11T07:25:34',(''),(''),'','','');
FILE_SCHEMA(('AUTOMOTIVE_DESIGN'));
ENDSEC;
DATA;

View File

@ -0,0 +1,73 @@
@echo off
set pathofEDASourceFile=%1
set FilePath=%~dp0
::delete --show-dialog after first start up and setting
set option=--show-dialog
::detect current language of user.
reg query "HKCU\Control Panel\Desktop" /v PreferredUILanguages>nul 2>nul&&goto _dosearch1_||goto _dosearch2_
:_dosearch1_
FOR /F "tokens=3" %%a IN (
'reg query "HKCU\Control Panel\Desktop" /v PreferredUILanguages ^| find "PreferredUILanguages"'
) DO (
set language=%%a
)
set language=%language:~,2%
goto _setlanguage_
:_dosearch2_
FOR /F "tokens=3" %%a IN (
'reg query "HKLM\SYSTEM\ControlSet001\Control\Nls\Language" /v InstallLanguage ^| find "InstallLanguage"'
) DO (
set language=%%a
)
if %language%==0804 (
set language=zh
)
goto _setlanguage_
:_setlanguage_
if %language%==zh (
call %FilePath%\i18n\language_zh.bat
) else (
call %FilePath%\i18n\language_en.bat
)
cls
echo -------------------------------------------------------------------------------------------------------------------
echo -------------------------------------------------------------------------------------------------------------------
echo.
echo %i18n_thx4using%
echo %i18n_gitAddr%
echo %i18n_batScar%
echo.
echo -------------------------------------------------------------------------------------------------------------------
echo -------------------------------------------------------------------------------------------------------------------
set pyFilePath=%FilePath%generate_interactive_bom.py
:_convert_
if not defined pathofEDASourceFile (
set /p pathofEDASourceFile=%i18n_draghere%
)
echo.
echo %i18n_converting%
echo.
python %pyFilePath% %pathofEDASourceFile% %option%
set pathofEDASourceFile=
echo -------------------------------------------------------------------------------------------------------------------
echo -------------------------------------------------------------------------------------------------------------------
echo.
echo %i18n_converted%
echo.
echo -------------------------------------------------------------------------------------------------------------------
echo -------------------------------------------------------------------------------------------------------------------
CHOICE /C YN /N /M "%i18n_again% [ Y/N ]"
if errorlevel 2 exit
if errorlevel 1 goto _convert_

View File

@ -0,0 +1,58 @@
import os
import sys
import threading
import time
import wx
import wx.aui
def check_for_bom_button():
# From Miles McCoo's blog
# https://kicad.mmccoo.com/2017/03/05/adding-your-own-command-buttons-to-the-pcbnew-gui/
def find_pcbnew_window():
windows = wx.GetTopLevelWindows()
pcbneww = [w for w in windows if "pcbnew" in w.GetTitle().lower()]
if len(pcbneww) != 1:
return None
return pcbneww[0]
def callback(_):
plugin.Run()
path = os.path.dirname(__file__)
while not wx.GetApp():
time.sleep(1)
bm = wx.Bitmap(path + '/icon.png', wx.BITMAP_TYPE_PNG)
button_wx_item_id = 0
from pcbnew import ID_H_TOOLBAR
while True:
time.sleep(1)
pcbnew_window = find_pcbnew_window()
if not pcbnew_window:
continue
top_tb = pcbnew_window.FindWindowById(ID_H_TOOLBAR)
if button_wx_item_id == 0 or not top_tb.FindTool(button_wx_item_id):
top_tb.AddSeparator()
button_wx_item_id = wx.NewId()
top_tb.AddTool(button_wx_item_id, "iBOM", bm,
"Generate interactive BOM", wx.ITEM_NORMAL)
top_tb.Bind(wx.EVT_TOOL, callback, id=button_wx_item_id)
top_tb.Realize()
if (not os.environ.get('INTERACTIVE_HTML_BOM_CLI_MODE', False) and
os.path.basename(sys.argv[0]) != 'generate_interactive_bom.py'):
from .ecad.kicad import InteractiveHtmlBomPlugin
plugin = InteractiveHtmlBomPlugin()
plugin.register()
# Add a button the hacky way if plugin button is not supported
# in pcbnew, unless this is linux.
if not plugin.pcbnew_icon_support and not sys.platform.startswith('linux'):
t = threading.Thread(target=check_for_bom_button)
t.daemon = True
t.start()

View File

@ -0,0 +1,468 @@
"""Config object"""
import argparse
import os
import re
from wx import FileConfig
from .. import dialog
class Config:
FILE_NAME_FORMAT_HINT = (
'Output file name format supports substitutions:\n'
'\n'
' %f : original pcb file name without extension.\n'
' %p : pcb/project title from pcb metadata.\n'
' %c : company from pcb metadata.\n'
' %r : revision from pcb metadata.\n'
' %d : pcb date from metadata if available, '
'file modification date otherwise.\n'
' %D : bom generation date.\n'
' %T : bom generation time.\n'
'\n'
'Extension .html will be added automatically.'
) # type: str
# Helper constants
bom_view_choices = ['bom-only', 'left-right', 'top-bottom']
layer_view_choices = ['F', 'FB', 'B']
default_sort_order = [
'C', 'R', 'L', 'D', 'U', 'Y', 'X', 'F', 'SW', 'A',
'~',
'HS', 'CNN', 'J', 'P', 'NT', 'MH',
]
default_checkboxes = ['Sourced', 'Placed']
html_config_fields = [
'dark_mode', 'show_pads', 'show_fabrication', 'show_silkscreen',
'highlight_pin1', 'redraw_on_drag', 'board_rotation', 'checkboxes',
'bom_view', 'layer_view', 'offset_back_rotation',
'kicad_text_formatting'
]
default_show_group_fields = ["Value", "Footprint"]
# Defaults
# HTML section
dark_mode = False
show_pads = True
show_fabrication = False
show_silkscreen = True
highlight_pin1 = False
redraw_on_drag = True
board_rotation = 0
offset_back_rotation = False
checkboxes = ','.join(default_checkboxes)
bom_view = bom_view_choices[1]
layer_view = layer_view_choices[1]
compression = True
open_browser = True
# General section
bom_dest_dir = 'bom/' # This is relative to pcb file directory
bom_name_format = 'ibom'
component_sort_order = default_sort_order
component_blacklist = []
blacklist_virtual = True
blacklist_empty_val = False
include_tracks = False
include_nets = False
kicad_text_formatting = True
# Extra fields section
extra_data_file = None
netlist_initial_directory = '' # This is relative to pcb file directory
show_fields = default_show_group_fields
group_fields = default_show_group_fields
normalize_field_case = False
board_variant_field = ''
board_variant_whitelist = []
board_variant_blacklist = []
dnp_field = ''
@staticmethod
def _split(s):
"""Splits string by ',' and drops empty strings from resulting array"""
return [a.replace('\\,', ',') for a in re.split(r'(?<!\\),', s) if a]
@staticmethod
def _join(lst):
return ','.join([s.replace(',', '\\,') for s in lst])
def __init__(self, version, local_dir):
self.version = version
self.local_config_file = os.path.join(local_dir, 'ibom.config.ini')
self.global_config_file = os.path.join(
os.path.dirname(__file__), '..', 'config.ini')
def load_from_ini(self):
"""Init from config file if it exists."""
if os.path.isfile(self.local_config_file):
file = self.local_config_file
elif os.path.isfile(self.global_config_file):
file = self.global_config_file
else:
return
f = FileConfig(localFilename=file)
f.SetPath('/html_defaults')
self.dark_mode = f.ReadBool('dark_mode', self.dark_mode)
self.show_pads = f.ReadBool('show_pads', self.show_pads)
self.show_fabrication = f.ReadBool(
'show_fabrication', self.show_fabrication)
self.show_silkscreen = f.ReadBool(
'show_silkscreen', self.show_silkscreen)
self.highlight_pin1 = f.ReadBool('highlight_pin1', self.highlight_pin1)
self.redraw_on_drag = f.ReadBool('redraw_on_drag', self.redraw_on_drag)
self.board_rotation = f.ReadInt('board_rotation', self.board_rotation)
self.offset_back_rotation = f.ReadBool(
'offset_back_rotation', self.offset_back_rotation)
self.checkboxes = f.Read('checkboxes', self.checkboxes)
self.bom_view = f.Read('bom_view', self.bom_view)
self.layer_view = f.Read('layer_view', self.layer_view)
self.compression = f.ReadBool('compression', self.compression)
self.open_browser = f.ReadBool('open_browser', self.open_browser)
f.SetPath('/general')
self.bom_dest_dir = f.Read('bom_dest_dir', self.bom_dest_dir)
self.bom_name_format = f.Read('bom_name_format', self.bom_name_format)
self.component_sort_order = self._split(f.Read(
'component_sort_order',
','.join(self.component_sort_order)))
self.component_blacklist = self._split(f.Read(
'component_blacklist',
','.join(self.component_blacklist)))
self.blacklist_virtual = f.ReadBool(
'blacklist_virtual', self.blacklist_virtual)
self.blacklist_empty_val = f.ReadBool(
'blacklist_empty_val', self.blacklist_empty_val)
self.include_tracks = f.ReadBool('include_tracks', self.include_tracks)
self.include_nets = f.ReadBool('include_nets', self.include_nets)
f.SetPath('/fields')
self.show_fields = self._split(f.Read(
'show_fields', self._join(self.show_fields)))
self.group_fields = self._split(f.Read(
'group_fields', self._join(self.group_fields)))
self.normalize_field_case = f.ReadBool(
'normalize_field_case', self.normalize_field_case)
self.board_variant_field = f.Read(
'board_variant_field', self.board_variant_field)
self.board_variant_whitelist = self._split(f.Read(
'board_variant_whitelist',
self._join(self.board_variant_whitelist)))
self.board_variant_blacklist = self._split(f.Read(
'board_variant_blacklist',
self._join(self.board_variant_blacklist)))
self.dnp_field = f.Read('dnp_field', self.dnp_field)
def save(self, locally):
file = self.local_config_file if locally else self.global_config_file
print('Saving to', file)
f = FileConfig(localFilename=file)
f.SetPath('/html_defaults')
f.WriteBool('dark_mode', self.dark_mode)
f.WriteBool('show_pads', self.show_pads)
f.WriteBool('show_fabrication', self.show_fabrication)
f.WriteBool('show_silkscreen', self.show_silkscreen)
f.WriteBool('highlight_pin1', self.highlight_pin1)
f.WriteBool('redraw_on_drag', self.redraw_on_drag)
f.WriteInt('board_rotation', self.board_rotation)
f.WriteBool('offset_back_rotation', self.offset_back_rotation)
f.Write('checkboxes', self.checkboxes)
f.Write('bom_view', self.bom_view)
f.Write('layer_view', self.layer_view)
f.WriteBool('compression', self.compression)
f.WriteBool('open_browser', self.open_browser)
f.SetPath('/general')
bom_dest_dir = self.bom_dest_dir
if bom_dest_dir.startswith(self.netlist_initial_directory):
bom_dest_dir = os.path.relpath(
bom_dest_dir, self.netlist_initial_directory)
f.Write('bom_dest_dir', bom_dest_dir)
f.Write('bom_name_format', self.bom_name_format)
f.Write('component_sort_order',
','.join(self.component_sort_order))
f.Write('component_blacklist',
','.join(self.component_blacklist))
f.WriteBool('blacklist_virtual', self.blacklist_virtual)
f.WriteBool('blacklist_empty_val', self.blacklist_empty_val)
f.WriteBool('include_tracks', self.include_tracks)
f.WriteBool('include_nets', self.include_nets)
f.SetPath('/fields')
f.Write('show_fields', self._join(self.show_fields))
f.Write('group_fields', self._join(self.group_fields))
f.WriteBool('normalize_field_case', self.normalize_field_case)
f.Write('board_variant_field', self.board_variant_field)
f.Write('board_variant_whitelist',
self._join(self.board_variant_whitelist))
f.Write('board_variant_blacklist',
self._join(self.board_variant_blacklist))
f.Write('dnp_field', self.dnp_field)
f.Flush()
def set_from_dialog(self, dlg):
# type: (dialog.settings_dialog.SettingsDialogPanel) -> None
# Html
self.dark_mode = dlg.html.darkModeCheckbox.IsChecked()
self.show_pads = dlg.html.showPadsCheckbox.IsChecked()
self.show_fabrication = dlg.html.showFabricationCheckbox.IsChecked()
self.show_silkscreen = dlg.html.showSilkscreenCheckbox.IsChecked()
self.highlight_pin1 = dlg.html.highlightPin1Checkbox.IsChecked()
self.redraw_on_drag = dlg.html.continuousRedrawCheckbox.IsChecked()
self.board_rotation = dlg.html.boardRotationSlider.Value
self.offset_back_rotation = \
dlg.html.offsetBackRotationCheckbox.IsChecked()
self.checkboxes = dlg.html.bomCheckboxesCtrl.Value
self.bom_view = self.bom_view_choices[dlg.html.bomDefaultView.Selection]
self.layer_view = self.layer_view_choices[
dlg.html.layerDefaultView.Selection]
self.compression = dlg.html.compressionCheckbox.IsChecked()
self.open_browser = dlg.html.openBrowserCheckbox.IsChecked()
# General
self.bom_dest_dir = dlg.general.bomDirPicker.Path
self.bom_name_format = dlg.general.fileNameFormatTextControl.Value
self.component_sort_order = dlg.general.componentSortOrderBox.GetItems()
self.component_blacklist = dlg.general.blacklistBox.GetItems()
self.blacklist_virtual = \
dlg.general.blacklistVirtualCheckbox.IsChecked()
self.blacklist_empty_val = \
dlg.general.blacklistEmptyValCheckbox.IsChecked()
self.include_tracks = dlg.general.includeTracksCheckbox.IsChecked()
self.include_nets = dlg.general.includeNetsCheckbox.IsChecked()
# Fields
self.extra_data_file = dlg.fields.extraDataFilePicker.Path
self.show_fields = dlg.fields.GetShowFields()
self.group_fields = dlg.fields.GetGroupFields()
self.normalize_field_case = dlg.fields.normalizeCaseCheckbox.Value
self.board_variant_field = dlg.fields.boardVariantFieldBox.Value
if self.board_variant_field == dlg.fields.NONE_STRING:
self.board_variant_field = ''
self.board_variant_whitelist = list(
dlg.fields.boardVariantWhitelist.GetCheckedStrings())
self.board_variant_blacklist = list(
dlg.fields.boardVariantBlacklist.GetCheckedStrings())
self.dnp_field = dlg.fields.dnpFieldBox.Value
if self.dnp_field == dlg.fields.NONE_STRING:
self.dnp_field = ''
def transfer_to_dialog(self, dlg):
# type: (dialog.settings_dialog.SettingsDialogPanel) -> None
# Html
dlg.html.darkModeCheckbox.Value = self.dark_mode
dlg.html.showPadsCheckbox.Value = self.show_pads
dlg.html.showFabricationCheckbox.Value = self.show_fabrication
dlg.html.showSilkscreenCheckbox.Value = self.show_silkscreen
dlg.html.highlightPin1Checkbox.Value = self.highlight_pin1
dlg.html.continuousRedrawCheckbox.value = self.redraw_on_drag
dlg.html.boardRotationSlider.Value = self.board_rotation
dlg.html.offsetBackRotationCheckbox.Value = self.offset_back_rotation
dlg.html.bomCheckboxesCtrl.Value = self.checkboxes
dlg.html.bomDefaultView.Selection = self.bom_view_choices.index(
self.bom_view)
dlg.html.layerDefaultView.Selection = self.layer_view_choices.index(
self.layer_view)
dlg.html.compressionCheckbox.Value = self.compression
dlg.html.openBrowserCheckbox.Value = self.open_browser
# General
import os.path
if os.path.isabs(self.bom_dest_dir):
dlg.general.bomDirPicker.Path = self.bom_dest_dir
else:
dlg.general.bomDirPicker.Path = os.path.join(
self.netlist_initial_directory, self.bom_dest_dir)
dlg.general.fileNameFormatTextControl.Value = self.bom_name_format
dlg.general.componentSortOrderBox.SetItems(self.component_sort_order)
dlg.general.blacklistBox.SetItems(self.component_blacklist)
dlg.general.blacklistVirtualCheckbox.Value = self.blacklist_virtual
dlg.general.blacklistEmptyValCheckbox.Value = self.blacklist_empty_val
dlg.general.includeTracksCheckbox.Value = self.include_tracks
dlg.general.includeNetsCheckbox.Value = self.include_nets
# Fields
dlg.fields.extraDataFilePicker.SetInitialDirectory(
self.netlist_initial_directory)
def safe_set_checked_strings(clb, strings):
current = list(clb.GetStrings())
if current:
present_strings = [s for s in strings if s in current]
not_present_strings = [s for s in current if s not in strings]
clb.Clear()
clb.InsertItems(present_strings + not_present_strings, 0)
clb.SetCheckedStrings(present_strings)
dlg.fields.SetCheckedFields(self.show_fields, self.group_fields)
dlg.fields.normalizeCaseCheckbox.Value = self.normalize_field_case
dlg.fields.boardVariantFieldBox.Value = self.board_variant_field
dlg.fields.OnBoardVariantFieldChange(None)
safe_set_checked_strings(dlg.fields.boardVariantWhitelist,
self.board_variant_whitelist)
safe_set_checked_strings(dlg.fields.boardVariantBlacklist,
self.board_variant_blacklist)
dlg.fields.dnpFieldBox.Value = self.dnp_field
dlg.finish_init()
@classmethod
def add_options(cls, parser, version):
# type: (argparse.ArgumentParser, str) -> None
parser.add_argument('--show-dialog', action='store_true',
help='Shows config dialog. All other flags '
'will be ignored.')
parser.add_argument('--version', action='version', version=version)
# Html
parser.add_argument('--dark-mode', help='Default to dark mode.',
action='store_true')
parser.add_argument('--hide-pads',
help='Hide footprint pads by default.',
action='store_true')
parser.add_argument('--show-fabrication',
help='Show fabrication layer by default.',
action='store_true')
parser.add_argument('--hide-silkscreen',
help='Hide silkscreen by default.',
action='store_true')
parser.add_argument('--highlight-pin1',
help='Highlight pin1 by default.',
action='store_true')
parser.add_argument('--no-redraw-on-drag',
help='Do not redraw pcb on drag by default.',
action='store_true')
parser.add_argument('--board-rotation', type=int,
default=cls.board_rotation * 5,
help='Board rotation in degrees (-180 to 180). '
'Will be rounded to multiple of 5.')
parser.add_argument('--offset-back-rotation',
help='Offset the back of the pcb by 180 degrees',
action='store_true')
parser.add_argument('--checkboxes',
default=cls.checkboxes,
help='Comma separated list of checkbox columns.')
parser.add_argument('--bom-view', default=cls.bom_view,
choices=cls.bom_view_choices,
help='Default BOM view.')
parser.add_argument('--layer-view', default=cls.layer_view,
choices=cls.layer_view_choices,
help='Default layer view.')
parser.add_argument('--no-compression',
help='Disable compression of pcb data.',
action='store_true')
parser.add_argument('--no-browser', help='Do not launch browser.',
action='store_true')
# General
parser.add_argument('--dest-dir', default=cls.bom_dest_dir,
help='Destination directory for bom file '
'relative to pcb file directory.')
parser.add_argument('--name-format', default=cls.bom_name_format,
help=cls.FILE_NAME_FORMAT_HINT.replace('%', '%%'))
parser.add_argument('--include-tracks', action='store_true',
help='Include track/zone information in output. '
'F.Cu and B.Cu layers only.')
parser.add_argument('--include-nets', action='store_true',
help='Include netlist information in output.')
parser.add_argument('--sort-order',
help='Default sort order for components. '
'Must contain "~" once.',
default=','.join(cls.component_sort_order))
parser.add_argument('--blacklist',
default=','.join(cls.component_blacklist),
help='List of comma separated blacklisted '
'components or prefixes with *. '
'E.g. "X1,MH*"')
parser.add_argument('--no-blacklist-virtual', action='store_true',
help='Do not blacklist virtual components.')
parser.add_argument('--blacklist-empty-val', action='store_true',
help='Blacklist components with empty value.')
# Fields section
parser.add_argument('--netlist-file',
help='(Deprecated) Path to netlist or xml file.')
parser.add_argument('--extra-data-file',
help='Path to netlist or xml file.')
parser.add_argument('--extra-fields',
help='Passing --extra-fields "X,Y" is a shortcut '
'for --show-fields and --group-fields '
'with values "Value,Footprint,X,Y"')
parser.add_argument('--show-fields',
default=cls._join(cls.show_fields),
help='List of fields to show in the BOM.')
parser.add_argument('--group-fields',
default=cls._join(cls.group_fields),
help='Fields that components will be grouped by.')
parser.add_argument('--normalize-field-case',
help='Normalize extra field name case. E.g. "MPN" '
', "mpn" will be considered the same field.',
action='store_true')
parser.add_argument('--variant-field',
help='Name of the extra field that stores board '
'variant for component.')
parser.add_argument('--variants-whitelist', default='',
help='List of board variants to '
'include in the BOM.')
parser.add_argument('--variants-blacklist', default='',
help='List of board variants to '
'exclude from the BOM.')
parser.add_argument('--dnp-field', default=cls.dnp_field,
help='Name of the extra field that indicates '
'do not populate status. Components with '
'this field not empty will be excluded.')
def set_from_args(self, args):
# type: (argparse.Namespace) -> None
import math
# Html
self.dark_mode = args.dark_mode
self.show_pads = not args.hide_pads
self.show_fabrication = args.show_fabrication
self.show_silkscreen = not args.hide_silkscreen
self.highlight_pin1 = args.highlight_pin1
self.redraw_on_drag = not args.no_redraw_on_drag
self.board_rotation = math.fmod(args.board_rotation // 5, 37)
self.offset_back_rotation = args.offset_back_rotation
self.checkboxes = args.checkboxes
self.bom_view = args.bom_view
self.layer_view = args.layer_view
self.compression = not args.no_compression
self.open_browser = not args.no_browser
# General
self.bom_dest_dir = args.dest_dir
self.bom_name_format = args.name_format
self.component_sort_order = self._split(args.sort_order)
self.component_blacklist = self._split(args.blacklist)
self.blacklist_virtual = not args.no_blacklist_virtual
self.blacklist_empty_val = args.blacklist_empty_val
self.include_tracks = args.include_tracks
self.include_nets = args.include_nets
# Fields
self.extra_data_file = args.extra_data_file or args.netlist_file
if args.extra_fields is not None:
self.show_fields = self.default_show_group_fields + \
self._split(args.extra_fields)
self.group_fields = self.show_fields
else:
self.show_fields = self._split(args.show_fields)
self.group_fields = self._split(args.group_fields)
self.normalize_field_case = args.normalize_field_case
self.board_variant_field = args.variant_field
self.board_variant_whitelist = self._split(args.variants_whitelist)
self.board_variant_blacklist = self._split(args.variants_blacklist)
self.dnp_field = args.dnp_field
def get_html_config(self):
import json
d = {f: getattr(self, f) for f in self.html_config_fields}
d["fields"] = self.show_fields
return json.dumps(d)

View File

@ -0,0 +1,52 @@
from .newstroke_font import NEWSTROKE_FONT
class FontParser:
STROKE_FONT_SCALE = 1.0 / 21.0
FONT_OFFSET = -10
def __init__(self):
self.parsed_font = {}
def parse_font_char(self, chr):
lines = []
line = []
glyph_x = 0
index = ord(chr) - ord(' ')
if index >= len(NEWSTROKE_FONT):
index = ord('?') - ord(' ')
glyph_str = NEWSTROKE_FONT[index]
for i in range(0, len(glyph_str), 2):
coord = glyph_str[i:i + 2]
# The first two values contain the width of the char
if i < 2:
glyph_x = (ord(coord[0]) - ord('R')) * self.STROKE_FONT_SCALE
glyph_width = (ord(coord[1]) - ord(coord[0])) * self.STROKE_FONT_SCALE
elif coord[0] == ' ' and coord[1] == 'R':
lines.append(line)
line = []
else:
line.append([
(ord(coord[0]) - ord('R')) * self.STROKE_FONT_SCALE - glyph_x,
(ord(coord[1]) - ord('R') + self.FONT_OFFSET) * self.STROKE_FONT_SCALE
])
if len(line) > 0:
lines.append(line)
return {
'w': glyph_width,
'l': lines
}
def parse_font_for_string(self, s):
for c in s:
if c == '\t' and ' ' not in self.parsed_font:
# tabs rely on space char to calculate offset
self.parsed_font[' '] = self.parse_font_char(' ')
if c not in self.parsed_font and ord(c) >= ord(' '):
self.parsed_font[c] = self.parse_font_char(c)
def get_parsed_font(self):
return self.parsed_font

View File

@ -0,0 +1,357 @@
from __future__ import absolute_import
import io
import json
import logging
import os
import re
import sys
from datetime import datetime
import wx
from . import units
from .config import Config
from ..dialog import SettingsDialog
from ..ecad.common import EcadParser, Component
from ..errors import ParsingException
class Logger(object):
def __init__(self, cli=False):
self.cli = cli
self.logger = logging.getLogger('InteractiveHtmlBom')
self.logger.setLevel(logging.INFO)
ch = logging.StreamHandler(sys.stdout)
ch.setLevel(logging.INFO)
formatter = logging.Formatter(
"%(asctime)-15s %(levelname)s %(message)s")
ch.setFormatter(formatter)
self.logger.addHandler(ch)
def info(self, *args):
if self.cli:
self.logger.info(*args)
def error(self, msg):
if self.cli:
self.logger.error(msg)
else:
wx.MessageBox(msg)
def warn(self, msg):
if self.cli:
self.logger.warning(msg)
else:
wx.LogWarning(msg)
log = None
def skip_component(m, config):
# type: (Component, Config) -> bool
# skip blacklisted components
ref_prefix = re.findall('^[A-Z]*', m.ref)[0]
if m.ref in config.component_blacklist:
return True
if ref_prefix + '*' in config.component_blacklist:
return True
if config.blacklist_empty_val and m.val in ['', '~']:
return True
# skip virtual components if needed
if config.blacklist_virtual and m.attr == 'Virtual':
return True
# skip components with dnp field not empty
if config.dnp_field \
and config.dnp_field in m.extra_fields \
and m.extra_fields[config.dnp_field]:
return True
# skip components with wrong variant field
if config.board_variant_field and config.board_variant_whitelist:
ref_variant = m.extra_fields.get(config.board_variant_field, '')
if ref_variant not in config.board_variant_whitelist:
return True
if config.board_variant_field and config.board_variant_blacklist:
ref_variant = m.extra_fields.get(config.board_variant_field, '')
if ref_variant and ref_variant in config.board_variant_blacklist:
return True
return False
def generate_bom(pcb_footprints, config):
# type: (list, Config) -> dict
"""
Generate BOM from pcb layout.
:param pcb_footprints: list of footprints on the pcb
:param config: Config object
:return: dict of BOM tables (qty, value, footprint, refs)
and dnp components
"""
def convert(text):
return int(text) if text.isdigit() else text.lower()
def alphanum_key(key):
return [convert(c)
for c in re.split('([0-9]+)', key)]
def natural_sort(lst):
"""
Natural sort for strings containing numbers
"""
return sorted(lst, key=lambda r: (alphanum_key(r[0]), r[1]))
# build grouped part list
skipped_components = []
part_groups = {}
group_by = set(config.group_fields)
index_to_fields = {}
for i, f in enumerate(pcb_footprints):
if skip_component(f, config):
skipped_components.append(i)
continue
# group part refs by value and footprint
fields = []
group_key = []
for field in config.show_fields:
if field == "Value":
fields.append(f.val)
if "Value" in group_by:
norm_value, unit = units.componentValue(f.val, f.ref)
group_key.append(norm_value)
group_key.append(unit)
elif field == "Footprint":
fields.append(f.footprint)
if "Footprint" in group_by:
group_key.append(f.footprint)
group_key.append(f.attr)
else:
fields.append(f.extra_fields.get(field, ''))
if field in group_by:
group_key.append(f.extra_fields.get(field, ''))
index_to_fields[i] = fields
refs = part_groups.setdefault(tuple(group_key), [])
refs.append((f.ref, i))
bom_table = []
# If some extra fields are just integers then convert the whole column
# so that sorting will work naturally
for i, field in enumerate(config.show_fields):
if field in ["Value", "Footprint"]:
continue
all_num = True
for f in index_to_fields.values():
if not f[i].isdigit() and len(f[i].strip()) > 0:
all_num = False
break
if all_num:
for f in index_to_fields.values():
if f[i].isdigit():
f[i] = int(f[i])
for _, refs in part_groups.items():
# Fixup values to normalized string
if "Value" in group_by and "Value" in config.show_fields:
index = config.show_fields.index("Value")
value = index_to_fields[refs[0][1]][index]
for ref in refs:
index_to_fields[ref[1]][index] = value
bom_table.append(natural_sort(refs))
# sort table by reference prefix and quantity
def row_sort_key(element):
prefix = re.findall('^[^0-9]*', element[0][0])[0]
if prefix in config.component_sort_order:
ref_ord = config.component_sort_order.index(prefix)
else:
ref_ord = config.component_sort_order.index('~')
return ref_ord, -len(element), alphanum_key(element[0][0])
if '~' not in config.component_sort_order:
config.component_sort_order.append('~')
bom_table = sorted(bom_table, key=row_sort_key)
result = {
'both': bom_table,
'skipped': skipped_components,
'fields': index_to_fields
}
for layer in ['F', 'B']:
filtered_table = []
for row in bom_table:
filtered_refs = [ref for ref in row
if pcb_footprints[ref[1]].layer == layer]
if filtered_refs:
filtered_table.append(filtered_refs)
result[layer] = sorted(filtered_table, key=row_sort_key)
return result
def open_file(filename):
import subprocess
try:
if sys.platform.startswith('win'):
os.startfile(filename)
elif sys.platform.startswith('darwin'):
subprocess.call(('open', filename))
elif sys.platform.startswith('linux'):
subprocess.call(('xdg-open', filename))
except Exception as e:
log.warn('Failed to open browser: {}'.format(e))
def process_substitutions(bom_name_format, pcb_file_name, metadata):
# type: (str, str, dict)->str
name = bom_name_format.replace('%f', os.path.splitext(pcb_file_name)[0])
name = name.replace('%p', metadata['title'])
name = name.replace('%c', metadata['company'])
name = name.replace('%r', metadata['revision'])
name = name.replace('%d', metadata['date'].replace(':', '-'))
now = datetime.now()
name = name.replace('%D', now.strftime('%Y-%m-%d'))
name = name.replace('%T', now.strftime('%H-%M-%S'))
# sanitize the name to avoid characters illegal in file systems
name = name.replace('\\', '/')
name = re.sub(r'[?%*:|"<>]', '_', name)
return name + '.html'
def round_floats(o, precision):
if isinstance(o, float):
return round(o, precision)
if isinstance(o, dict):
return {k: round_floats(v, precision) for k, v in o.items()}
if isinstance(o, (list, tuple)):
return [round_floats(x, precision) for x in o]
return o
def get_pcbdata_javascript(pcbdata, compression):
from .lzstring import LZString
js = "var pcbdata = {}"
pcbdata_str = json.dumps(round_floats(pcbdata, 6))
if compression:
log.info("Compressing pcb data")
pcbdata_str = json.dumps(LZString().compress_to_base64(pcbdata_str))
js = "var pcbdata = JSON.parse(LZString.decompressFromBase64({}))"
return js.format(pcbdata_str)
def generate_file(pcb_file_dir, pcb_file_name, pcbdata, config):
def get_file_content(file_name):
path = os.path.join(os.path.dirname(__file__), "..", "web", file_name)
if not os.path.exists(path):
return ""
with io.open(path, 'r', encoding='utf-8') as f:
return f.read()
if os.path.isabs(config.bom_dest_dir):
bom_file_dir = config.bom_dest_dir
else:
bom_file_dir = os.path.join(pcb_file_dir, config.bom_dest_dir)
bom_file_name = process_substitutions(
config.bom_name_format, pcb_file_name, pcbdata['metadata'])
bom_file_name = os.path.join(bom_file_dir, bom_file_name)
bom_file_dir = os.path.dirname(bom_file_name)
if not os.path.isdir(bom_file_dir):
os.makedirs(bom_file_dir)
pcbdata_js = get_pcbdata_javascript(pcbdata, config.compression)
log.info("Dumping pcb data")
config_js = "var config = " + config.get_html_config()
html = get_file_content("ibom.html")
html = html.replace('///CSS///', get_file_content('ibom.css'))
html = html.replace('///USERCSS///', get_file_content('user.css'))
html = html.replace('///SPLITJS///', get_file_content('split.js'))
html = html.replace('///LZ-STRING///',
get_file_content('lz-string.js')
if config.compression else '')
html = html.replace('///POINTER_EVENTS_POLYFILL///',
get_file_content('pep.js'))
html = html.replace('///CONFIG///', config_js)
html = html.replace('///UTILJS///', get_file_content('util.js'))
html = html.replace('///RENDERJS///', get_file_content('render.js'))
html = html.replace('///TABLEUTILJS///', get_file_content('table-util.js'))
html = html.replace('///IBOMJS///', get_file_content('ibom.js'))
html = html.replace('///USERJS///', get_file_content('user.js'))
html = html.replace('///USERHEADER///',
get_file_content('userheader.html'))
html = html.replace('///USERFOOTER///',
get_file_content('userfooter.html'))
# Replace pcbdata last for better performance.
html = html.replace('///PCBDATA///', pcbdata_js)
with io.open(bom_file_name, 'wt', encoding='utf-8') as bom:
bom.write(html)
log.info("Created file %s", bom_file_name)
return bom_file_name
def main(parser, config, logger):
# type: (EcadParser, Config, Logger) -> None
global log
log = logger
pcb_file_name = os.path.basename(parser.file_name)
pcb_file_dir = os.path.dirname(parser.file_name)
pcbdata, components = parser.parse()
if not pcbdata and not components:
raise ParsingException('Parsing failed.')
pcbdata["bom"] = generate_bom(components, config)
pcbdata["ibom_version"] = config.version
# build BOM
bom_file = generate_file(pcb_file_dir, pcb_file_name, pcbdata, config)
if config.open_browser:
logger.info("Opening file in browser")
open_file(bom_file)
def run_with_dialog(parser, config, logger):
# type: (EcadParser, Config, Logger) -> None
def save_config(dialog_panel, locally=False):
config.set_from_dialog(dialog_panel)
config.save(locally)
config.load_from_ini()
dlg = SettingsDialog(extra_data_func=parser.parse_extra_data,
extra_data_wildcard=parser.extra_data_file_filter(),
config_save_func=save_config,
file_name_format_hint=config.FILE_NAME_FORMAT_HINT,
version=config.version)
try:
config.netlist_initial_directory = os.path.dirname(parser.file_name)
extra_data_file = parser.latest_extra_data(
extra_dirs=[config.bom_dest_dir])
if extra_data_file is not None:
dlg.set_extra_data_path(extra_data_file)
config.transfer_to_dialog(dlg.panel)
if dlg.ShowModal() == wx.ID_OK:
config.set_from_dialog(dlg.panel)
main(parser, config, logger)
finally:
dlg.Destroy()

View File

@ -0,0 +1,304 @@
"""
Copyright 2014 Eduard Tomasek
This work is free. You can redistribute it and/or modify it under the
terms of the Do What The Fuck You Want To Public License, Version 2,
as published by Sam Hocevar. See the COPYING file for more details.
"""
import sys
if sys.version_info[0] == 3:
unichr = chr
class LZString:
def __init__(self):
self.keyStr = (
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
)
@staticmethod
def compress(uncompressed):
if uncompressed is None:
return ''
context_dictionary = {}
context_dictionary_to_create = {}
context_w = ''
context_enlarge_in = 2
context_dict_size = 3
context_num_bits = 2
context_data_string = ''
context_data_val = 0
context_data_position = 0
uncompressed = uncompressed
for ii in range(len(uncompressed)):
context_c = uncompressed[ii]
if context_c not in context_dictionary:
context_dictionary[context_c] = context_dict_size
context_dict_size += 1
context_dictionary_to_create[context_c] = True
context_wc = context_w + context_c
if context_wc in context_dictionary:
context_w = context_wc
else:
if context_w in context_dictionary_to_create:
if ord(context_w[0]) < 256:
for _ in range(context_num_bits):
context_data_val = (context_data_val << 1)
if context_data_position == 15:
context_data_position = 0
context_data_string += unichr(context_data_val)
context_data_val = 0
else:
context_data_position += 1
value = ord(context_w[0])
for i in range(8):
context_data_val = (
(context_data_val << 1) | (value & 1)
)
if context_data_position == 15:
context_data_position = 0
context_data_string += unichr(context_data_val)
context_data_val = 0
else:
context_data_position += 1
value = value >> 1
else:
value = 1
for i in range(context_num_bits):
context_data_val = (context_data_val << 1) | value
if context_data_position == 15:
context_data_position = 0
context_data_string += unichr(context_data_val)
context_data_val = 0
else:
context_data_position += 1
value = 0
value = ord(context_w[0])
for i in range(16):
context_data_val = (
(context_data_val << 1) | (value & 1)
)
if context_data_position == 15:
context_data_position = 0
context_data_string += unichr(context_data_val)
context_data_val = 0
else:
context_data_position += 1
value = value >> 1
context_enlarge_in -= 1
if context_enlarge_in == 0:
context_enlarge_in = pow(2, context_num_bits)
context_num_bits += 1
context_dictionary_to_create.pop(context_w, None)
# del context_dictionary_to_create[context_w]
else:
value = context_dictionary[context_w]
for i in range(context_num_bits):
context_data_val = (
(context_data_val << 1) | (value & 1)
)
if context_data_position == 15:
context_data_position = 0
context_data_string += unichr(context_data_val)
context_data_val = 0
else:
context_data_position += 1
value = value >> 1
context_enlarge_in -= 1
if context_enlarge_in == 0:
context_enlarge_in = pow(2, context_num_bits)
context_num_bits += 1
context_dictionary[context_wc] = context_dict_size
context_dict_size += 1
context_w = context_c
if context_w != '':
if context_w in context_dictionary_to_create:
if ord(context_w[0]) < 256:
for i in range(context_num_bits):
context_data_val = (context_data_val << 1)
if context_data_position == 15:
context_data_position = 0
context_data_string += unichr(context_data_val)
context_data_val = 0
else:
context_data_position += 1
value = ord(context_w[0])
for i in range(8):
context_data_val = (
(context_data_val << 1) | (value & 1)
)
if context_data_position == 15:
context_data_position = 0
context_data_string += unichr(context_data_val)
context_data_val = 0
else:
context_data_position += 1
value = value >> 1
else:
value = 1
for i in range(context_num_bits):
context_data_val = (context_data_val << 1) | value
if context_data_position == 15:
context_data_position = 0
context_data_string += unichr(context_data_val)
context_data_val = 0
else:
context_data_position += 1
value = 0
value = ord(context_w[0])
for i in range(16):
context_data_val = (
(context_data_val << 1) | (value & 1)
)
if context_data_position == 15:
context_data_position = 0
context_data_string += unichr(context_data_val)
context_data_val = 0
else:
context_data_position += 1
value = value >> 1
context_enlarge_in -= 1
if context_enlarge_in == 0:
context_enlarge_in = pow(2, context_num_bits)
context_num_bits += 1
context_dictionary_to_create.pop(context_w, None)
# del context_dictionary_to_create[context_w]
else:
value = context_dictionary[context_w]
for i in range(context_num_bits):
context_data_val = (context_data_val << 1) | (value & 1)
if context_data_position == 15:
context_data_position = 0
context_data_string += unichr(context_data_val)
context_data_val = 0
else:
context_data_position += 1
value = value >> 1
context_enlarge_in -= 1
if context_enlarge_in == 0:
context_num_bits += 1
value = 2
for i in range(context_num_bits):
context_data_val = (context_data_val << 1) | (value & 1)
if context_data_position == 15:
context_data_position = 0
context_data_string += unichr(context_data_val)
context_data_val = 0
else:
context_data_position += 1
value = value >> 1
context_data_val = (context_data_val << 1)
while context_data_position != 15:
context_data_position += 1
context_data_val = (context_data_val << 1)
context_data_string += unichr(context_data_val)
return context_data_string
def compress_to_base64(self, string):
if string is None:
return ''
output = ''
string = self.compress(string)
str_len = len(string)
for i in range(0, str_len * 2, 3):
if (i % 2) == 0:
chr1 = ord(string[i // 2]) >> 8
chr2 = ord(string[i // 2]) & 255
if (i / 2) + 1 < str_len:
chr3 = ord(string[(i // 2) + 1]) >> 8
else:
chr3 = None
else:
chr1 = ord(string[(i - 1) // 2]) & 255
if (i + 1) / 2 < str_len:
chr2 = ord(string[(i + 1) // 2]) >> 8
chr3 = ord(string[(i + 1) // 2]) & 255
else:
chr2 = None
chr3 = None
# python dont support bit operation with NaN like javascript
enc1 = chr1 >> 2
enc2 = (
((chr1 & 3) << 4) |
(chr2 >> 4 if chr2 is not None else 0)
)
enc3 = (
((chr2 & 15 if chr2 is not None else 0) << 2) |
(chr3 >> 6 if chr3 is not None else 0)
)
enc4 = (chr3 if chr3 is not None else 0) & 63
if chr2 is None:
enc3 = 64
enc4 = 64
elif chr3 is None:
enc4 = 64
output += (
self.keyStr[enc1] +
self.keyStr[enc2] +
self.keyStr[enc3] +
self.keyStr[enc4]
)
return output

View File

@ -0,0 +1,192 @@
# _*_ coding:utf-8 _*_
# Stolen from
# https://github.com/SchrodingersGat/KiBoM/blob/master/KiBOM/units.py
"""
This file contains a set of functions for matching values which may be written
in different formats e.g.
0.1uF = 100n (different suffix specified, one has missing unit)
0R1 = 0.1Ohm (Unit replaces decimal, different units)
"""
import re
import locale
current_locale = locale.setlocale(locale.LC_NUMERIC)
try:
locale.setlocale(locale.LC_NUMERIC, '')
except Exception:
# sometimes setlocale with empty string doesn't work on OSX
pass
decimal_separator = locale.localeconv()['decimal_point']
locale.setlocale(locale.LC_NUMERIC, current_locale)
PREFIX_MICRO = [u"μ", u"µ", "u", "micro"] # first is \u03BC second is \u00B5
PREFIX_MILLI = ["milli", "m"]
PREFIX_NANO = ["nano", "n"]
PREFIX_PICO = ["pico", "p"]
PREFIX_KILO = ["kilo", "k"]
PREFIX_MEGA = ["mega", "meg"]
PREFIX_GIGA = ["giga", "g"]
# All prefices
PREFIX_ALL = PREFIX_PICO + PREFIX_NANO + PREFIX_MICRO + \
PREFIX_MILLI + PREFIX_KILO + PREFIX_MEGA + PREFIX_GIGA
# Common methods of expressing component units
UNIT_R = ["r", "ohms", "ohm", u"Ω", u"ω"]
UNIT_C = ["farad", "f"]
UNIT_L = ["henry", "h"]
UNIT_ALL = UNIT_R + UNIT_C + UNIT_L
VALUE_REGEX = re.compile(
"^([0-9\\.]+)(" + "|".join(PREFIX_ALL) + ")*(" + "|".join(
UNIT_ALL) + ")*(\\d*)$")
REFERENCE_REGEX = re.compile("^(r|rv|c|l)(\\d+)$")
def getUnit(unit):
"""
Return a simplified version of a units string, for comparison purposes
"""
if not unit:
return None
unit = unit.lower()
if unit in UNIT_R:
return "R"
if unit in UNIT_C:
return "F"
if unit in UNIT_L:
return "H"
return None
def getPrefix(prefix):
"""
Return the (numerical) value of a given prefix
"""
if not prefix:
return 1
prefix = prefix.lower()
if prefix in PREFIX_PICO:
return 1.0e-12
if prefix in PREFIX_NANO:
return 1.0e-9
if prefix in PREFIX_MICRO:
return 1.0e-6
if prefix in PREFIX_MILLI:
return 1.0e-3
if prefix in PREFIX_KILO:
return 1.0e3
if prefix in PREFIX_MEGA:
return 1.0e6
if prefix in PREFIX_GIGA:
return 1.0e9
return 1
def compMatch(component):
"""
Return a normalized value and units for a given component value string
e.g. compMatch("10R2") returns (1000, R)
e.g. compMatch("3.3mOhm") returns (0.0033, R)
"""
component = component.strip().lower()
if decimal_separator == ',':
# replace separator with dot
component = component.replace(",", ".")
else:
# remove thousands separator
component = component.replace(",", "")
result = VALUE_REGEX.match(component)
if not result:
return None
if not len(result.groups()) == 4:
return None
value, prefix, units, post = result.groups()
# special case where units is in the middle of the string
# e.g. "0R05" for 0.05Ohm
# in this case, we will NOT have a decimal
# we will also have a trailing number
if post and "." not in value:
try:
value = float(int(value))
postValue = float(int(post)) / (10 ** len(post))
value = value * 1.0 + postValue
except ValueError:
return None
try:
val = float(value)
except ValueError:
return None
val = "{0:.15f}".format(val * 1.0 * getPrefix(prefix))
return (val, getUnit(units))
def componentValue(valString, reference):
# type: (str, str) -> tuple
result = compMatch(valString)
if not result:
return valString, None # return the same string back with `None` unit
if not len(result) == 2: # result length is incorrect
return valString, None # return the same string back with `None` unit
if result[1] is None:
# try to infer unit from reference
match = REFERENCE_REGEX.match(reference.lower())
if match and len(match.groups()) == 2:
prefix, _ = match.groups()
unit = None
if prefix in ['r', 'rv']:
unit = 'R'
if prefix == 'c':
unit = 'F'
if prefix == 'l':
unit = 'H'
result = (result[0], unit)
return result # (val,unit)
def compareValues(c1, c2):
r1 = compMatch(c1)
r2 = compMatch(c2)
if not r1 or not r2:
return False
(v1, u1) = r1
(v2, u2) = r2
if v1 == v2:
# values match
if u1 == u2:
return True # units match
if not u1:
return True # no units for component 1
if not u2:
return True # no units for component 2
return False

View File

@ -0,0 +1 @@
from .settings_dialog import SettingsDialog, GeneralSettingsPanel

View File

@ -0,0 +1,574 @@
# -*- coding: utf-8 -*-
###########################################################################
## Python code generated with wxFormBuilder (version 3.10.1-0-g8feb16b3)
## http://www.wxformbuilder.org/
##
## PLEASE DO *NOT* EDIT THIS FILE!
###########################################################################
import wx
import wx.xrc
import wx.grid
###########################################################################
## Class SettingsDialogBase
###########################################################################
class SettingsDialogBase ( wx.Dialog ):
def __init__( self, parent ):
wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = u"InteractiveHtmlBom", pos = wx.DefaultPosition, size = wx.Size( 463,497 ), style = wx.DEFAULT_DIALOG_STYLE|wx.STAY_ON_TOP|wx.BORDER_DEFAULT )
self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )
self.Centre( wx.BOTH )
def __del__( self ):
pass
###########################################################################
## Class SettingsDialogPanel
###########################################################################
class SettingsDialogPanel ( wx.Panel ):
def __init__( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 400,300 ), style = wx.TAB_TRAVERSAL, name = wx.EmptyString ):
wx.Panel.__init__ ( self, parent, id = id, pos = pos, size = size, style = style, name = name )
bSizer20 = wx.BoxSizer( wx.VERTICAL )
self.notebook = wx.Notebook( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.NB_TOP|wx.BORDER_DEFAULT )
bSizer20.Add( self.notebook, 1, wx.EXPAND |wx.ALL, 5 )
bSizer39 = wx.BoxSizer( wx.HORIZONTAL )
self.saveSettingsBtn = wx.Button( self, wx.ID_ANY, u"Save current settings...", wx.DefaultPosition, wx.DefaultSize, 0|wx.BORDER_DEFAULT )
bSizer39.Add( self.saveSettingsBtn, 0, wx.ALL, 5 )
bSizer39.Add( ( 50, 0), 1, wx.EXPAND, 5 )
self.generateBomBtn = wx.Button( self, wx.ID_ANY, u"Generate BOM", wx.DefaultPosition, wx.DefaultSize, 0|wx.BORDER_DEFAULT )
self.generateBomBtn.SetDefault()
bSizer39.Add( self.generateBomBtn, 0, wx.ALL, 5 )
self.cancelBtn = wx.Button( self, wx.ID_CANCEL, u"Cancel", wx.DefaultPosition, wx.DefaultSize, 0|wx.BORDER_DEFAULT )
bSizer39.Add( self.cancelBtn, 0, wx.ALL, 5 )
bSizer20.Add( bSizer39, 0, wx.EXPAND, 5 )
self.SetSizer( bSizer20 )
self.Layout()
# Connect Events
self.saveSettingsBtn.Bind( wx.EVT_BUTTON, self.OnSave )
self.generateBomBtn.Bind( wx.EVT_BUTTON, self.OnGenerateBom )
self.cancelBtn.Bind( wx.EVT_BUTTON, self.OnExit )
def __del__( self ):
pass
# Virtual event handlers, override them in your derived class
def OnSave( self, event ):
event.Skip()
def OnGenerateBom( self, event ):
event.Skip()
def OnExit( self, event ):
event.Skip()
###########################################################################
## Class HtmlSettingsPanelBase
###########################################################################
class HtmlSettingsPanelBase ( wx.Panel ):
def __init__( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( -1,-1 ), style = wx.TAB_TRAVERSAL, name = wx.EmptyString ):
wx.Panel.__init__ ( self, parent, id = id, pos = pos, size = size, style = style, name = name )
b_sizer = wx.BoxSizer( wx.VERTICAL )
self.darkModeCheckbox = wx.CheckBox( self, wx.ID_ANY, u"Dark mode", wx.DefaultPosition, wx.DefaultSize, 0 )
b_sizer.Add( self.darkModeCheckbox, 0, wx.ALL, 5 )
self.showPadsCheckbox = wx.CheckBox( self, wx.ID_ANY, u"Show footprint pads", wx.DefaultPosition, wx.DefaultSize, 0 )
self.showPadsCheckbox.SetValue(True)
b_sizer.Add( self.showPadsCheckbox, 0, wx.ALL, 5 )
self.showFabricationCheckbox = wx.CheckBox( self, wx.ID_ANY, u"Show fabrication layer", wx.DefaultPosition, wx.DefaultSize, 0 )
b_sizer.Add( self.showFabricationCheckbox, 0, wx.ALL, 5 )
self.showSilkscreenCheckbox = wx.CheckBox( self, wx.ID_ANY, u"Show silkscreen", wx.DefaultPosition, wx.DefaultSize, 0 )
self.showSilkscreenCheckbox.SetValue(True)
b_sizer.Add( self.showSilkscreenCheckbox, 0, wx.ALL, 5 )
self.highlightPin1Checkbox = wx.CheckBox( self, wx.ID_ANY, u"Highlight first pin", wx.DefaultPosition, wx.DefaultSize, 0 )
b_sizer.Add( self.highlightPin1Checkbox, 0, wx.ALL, 5 )
self.continuousRedrawCheckbox = wx.CheckBox( self, wx.ID_ANY, u"Continuous redraw on drag", wx.DefaultPosition, wx.DefaultSize, 0 )
self.continuousRedrawCheckbox.SetValue(True)
b_sizer.Add( self.continuousRedrawCheckbox, 0, wx.ALL, 5 )
bSizer18 = wx.BoxSizer( wx.VERTICAL )
bSizer19 = wx.BoxSizer( wx.HORIZONTAL )
self.m_boardRotationLabel = wx.StaticText( self, wx.ID_ANY, u"Board rotation", wx.DefaultPosition, wx.DefaultSize, 0 )
self.m_boardRotationLabel.Wrap( -1 )
bSizer19.Add( self.m_boardRotationLabel, 0, wx.ALL, 5 )
bSizer19.Add( ( 0, 0), 1, wx.EXPAND, 5 )
self.rotationDegreeLabel = wx.StaticText( self, wx.ID_ANY, u"0", wx.DefaultPosition, wx.Size( 30,-1 ), wx.ALIGN_RIGHT|wx.ST_NO_AUTORESIZE )
self.rotationDegreeLabel.Wrap( -1 )
bSizer19.Add( self.rotationDegreeLabel, 0, wx.ALL, 5 )
bSizer19.Add( ( 8, 0), 0, 0, 5 )
bSizer18.Add( bSizer19, 1, wx.EXPAND, 5 )
self.boardRotationSlider = wx.Slider( self, wx.ID_ANY, 0, -36, 36, wx.DefaultPosition, wx.DefaultSize, wx.SL_HORIZONTAL )
bSizer18.Add( self.boardRotationSlider, 0, wx.ALL|wx.EXPAND, 5 )
b_sizer.Add( bSizer18, 0, wx.EXPAND, 5 )
self.offsetBackRotationCheckbox = wx.CheckBox( self, wx.ID_ANY, u"Offset back rotation", wx.DefaultPosition, wx.DefaultSize, 0 )
b_sizer.Add( self.offsetBackRotationCheckbox, 0, wx.ALL, 5 )
sbSizer31 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"Checkboxes" ), wx.HORIZONTAL )
self.bomCheckboxesCtrl = wx.TextCtrl( sbSizer31.GetStaticBox(), wx.ID_ANY, u"Sourced,Placed", wx.DefaultPosition, wx.DefaultSize, 0 )
sbSizer31.Add( self.bomCheckboxesCtrl, 1, wx.ALL, 5 )
b_sizer.Add( sbSizer31, 0, wx.ALL|wx.EXPAND, 5 )
bomDefaultViewChoices = [ u"BOM only", u"BOM left, drawings right", u"BOM top, drawings bottom" ]
self.bomDefaultView = wx.RadioBox( self, wx.ID_ANY, u"BOM View", wx.DefaultPosition, wx.DefaultSize, bomDefaultViewChoices, 1, wx.RA_SPECIFY_COLS )
self.bomDefaultView.SetSelection( 1 )
b_sizer.Add( self.bomDefaultView, 0, wx.ALL|wx.EXPAND, 5 )
layerDefaultViewChoices = [ u"Front only", u"Front and Back", u"Back only" ]
self.layerDefaultView = wx.RadioBox( self, wx.ID_ANY, u"Layer View", wx.DefaultPosition, wx.DefaultSize, layerDefaultViewChoices, 1, wx.RA_SPECIFY_COLS )
self.layerDefaultView.SetSelection( 1 )
b_sizer.Add( self.layerDefaultView, 0, wx.ALL|wx.EXPAND, 5 )
sbSizer10 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"Miscellaneous" ), wx.VERTICAL )
self.compressionCheckbox = wx.CheckBox( sbSizer10.GetStaticBox(), wx.ID_ANY, u"Enable compression", wx.DefaultPosition, wx.DefaultSize, 0 )
self.compressionCheckbox.SetValue(True)
sbSizer10.Add( self.compressionCheckbox, 0, wx.ALL, 5 )
self.openBrowserCheckbox = wx.CheckBox( sbSizer10.GetStaticBox(), wx.ID_ANY, u"Open browser", wx.DefaultPosition, wx.DefaultSize, 0 )
self.openBrowserCheckbox.SetValue(True)
sbSizer10.Add( self.openBrowserCheckbox, 0, wx.ALL, 5 )
b_sizer.Add( sbSizer10, 1, wx.EXPAND, 5 )
self.SetSizer( b_sizer )
self.Layout()
b_sizer.Fit( self )
# Connect Events
self.boardRotationSlider.Bind( wx.EVT_SLIDER, self.OnBoardRotationSlider )
def __del__( self ):
pass
# Virtual event handlers, override them in your derived class
def OnBoardRotationSlider( self, event ):
event.Skip()
###########################################################################
## Class GeneralSettingsPanelBase
###########################################################################
class GeneralSettingsPanelBase ( wx.Panel ):
def __init__( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( -1,-1 ), style = wx.TAB_TRAVERSAL, name = wx.EmptyString ):
wx.Panel.__init__ ( self, parent, id = id, pos = pos, size = size, style = style, name = name )
bSizer32 = wx.BoxSizer( wx.VERTICAL )
sbSizer6 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"Bom destination" ), wx.VERTICAL )
fgSizer1 = wx.FlexGridSizer( 0, 2, 0, 0 )
fgSizer1.AddGrowableCol( 1 )
fgSizer1.SetFlexibleDirection( wx.BOTH )
fgSizer1.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED )
self.m_staticText8 = wx.StaticText( sbSizer6.GetStaticBox(), wx.ID_ANY, u"Directory", wx.DefaultPosition, wx.DefaultSize, 0 )
self.m_staticText8.Wrap( -1 )
fgSizer1.Add( self.m_staticText8, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
self.bomDirPicker = wx.DirPickerCtrl( sbSizer6.GetStaticBox(), wx.ID_ANY, wx.EmptyString, u"Select bom folder", wx.DefaultPosition, wx.DefaultSize, wx.DIRP_SMALL|wx.DIRP_USE_TEXTCTRL|wx.BORDER_SIMPLE )
fgSizer1.Add( self.bomDirPicker, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL|wx.EXPAND, 5 )
self.m_staticText9 = wx.StaticText( sbSizer6.GetStaticBox(), wx.ID_ANY, u"Name format", wx.DefaultPosition, wx.DefaultSize, 0 )
self.m_staticText9.Wrap( -1 )
fgSizer1.Add( self.m_staticText9, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
bSizer20 = wx.BoxSizer( wx.HORIZONTAL )
self.fileNameFormatTextControl = wx.TextCtrl( sbSizer6.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer20.Add( self.fileNameFormatTextControl, 1, wx.ALIGN_CENTER_VERTICAL|wx.BOTTOM|wx.LEFT|wx.TOP, 5 )
self.m_btnNameHint = wx.Button( sbSizer6.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT )
self.m_btnNameHint.SetMinSize( wx.Size( 30,30 ) )
bSizer20.Add( self.m_btnNameHint, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
fgSizer1.Add( bSizer20, 1, wx.EXPAND, 5 )
sbSizer6.Add( fgSizer1, 1, wx.EXPAND, 5 )
bSizer32.Add( sbSizer6, 0, wx.ALL|wx.EXPAND, 5 )
sbSizer9 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"Additional pcb data" ), wx.HORIZONTAL )
self.includeTracksCheckbox = wx.CheckBox( sbSizer9.GetStaticBox(), wx.ID_ANY, u"Include tracks/zones", wx.DefaultPosition, wx.DefaultSize, 0 )
sbSizer9.Add( self.includeTracksCheckbox, 1, wx.ALL, 5 )
self.includeNetsCheckbox = wx.CheckBox( sbSizer9.GetStaticBox(), wx.ID_ANY, u"Include nets", wx.DefaultPosition, wx.DefaultSize, 0 )
sbSizer9.Add( self.includeNetsCheckbox, 1, wx.ALL, 5 )
bSizer32.Add( sbSizer9, 0, wx.ALL|wx.EXPAND, 5 )
sortingSizer = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"Component sort order" ), wx.VERTICAL )
bSizer4 = wx.BoxSizer( wx.HORIZONTAL )
bSizer6 = wx.BoxSizer( wx.VERTICAL )
componentSortOrderBoxChoices = []
self.componentSortOrderBox = wx.ListBox( sortingSizer.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, componentSortOrderBoxChoices, wx.LB_SINGLE|wx.BORDER_SIMPLE )
bSizer6.Add( self.componentSortOrderBox, 1, wx.ALL|wx.EXPAND, 5 )
bSizer4.Add( bSizer6, 1, wx.EXPAND, 5 )
bSizer5 = wx.BoxSizer( wx.VERTICAL )
self.m_btnSortUp = wx.Button( sortingSizer.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT )
self.m_btnSortUp.SetMinSize( wx.Size( 30,30 ) )
bSizer5.Add( self.m_btnSortUp, 0, wx.ALL, 5 )
self.m_btnSortDown = wx.Button( sortingSizer.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT )
self.m_btnSortDown.SetMinSize( wx.Size( 30,30 ) )
bSizer5.Add( self.m_btnSortDown, 0, wx.ALL, 5 )
self.m_btnSortAdd = wx.Button( sortingSizer.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT )
self.m_btnSortAdd.SetMinSize( wx.Size( 30,30 ) )
bSizer5.Add( self.m_btnSortAdd, 0, wx.ALL, 5 )
self.m_btnSortRemove = wx.Button( sortingSizer.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT )
self.m_btnSortRemove.SetMinSize( wx.Size( 30,30 ) )
bSizer5.Add( self.m_btnSortRemove, 0, wx.ALL, 5 )
bSizer4.Add( bSizer5, 0, 0, 5 )
sortingSizer.Add( bSizer4, 1, wx.EXPAND, 5 )
bSizer32.Add( sortingSizer, 1, wx.ALL|wx.EXPAND, 5 )
blacklistSizer = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"Component blacklist" ), wx.VERTICAL )
bSizer412 = wx.BoxSizer( wx.HORIZONTAL )
bSizer612 = wx.BoxSizer( wx.VERTICAL )
blacklistBoxChoices = []
self.blacklistBox = wx.ListBox( blacklistSizer.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, blacklistBoxChoices, wx.LB_SINGLE|wx.LB_SORT|wx.BORDER_SIMPLE )
bSizer612.Add( self.blacklistBox, 1, wx.ALL|wx.EXPAND, 5 )
bSizer412.Add( bSizer612, 1, wx.EXPAND, 5 )
bSizer512 = wx.BoxSizer( wx.VERTICAL )
self.m_btnBlacklistAdd = wx.Button( blacklistSizer.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT )
self.m_btnBlacklistAdd.SetMinSize( wx.Size( 30,30 ) )
bSizer512.Add( self.m_btnBlacklistAdd, 0, wx.ALL, 5 )
self.m_btnBlacklistRemove = wx.Button( blacklistSizer.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT )
self.m_btnBlacklistRemove.SetMinSize( wx.Size( 30,30 ) )
bSizer512.Add( self.m_btnBlacklistRemove, 0, wx.ALL, 5 )
bSizer412.Add( bSizer512, 0, 0, 5 )
blacklistSizer.Add( bSizer412, 1, wx.EXPAND, 5 )
self.m_staticText1 = wx.StaticText( blacklistSizer.GetStaticBox(), wx.ID_ANY, u"Globs are supported, e.g. MH*", wx.DefaultPosition, wx.DefaultSize, 0 )
self.m_staticText1.Wrap( -1 )
blacklistSizer.Add( self.m_staticText1, 0, wx.ALL, 5 )
self.blacklistVirtualCheckbox = wx.CheckBox( blacklistSizer.GetStaticBox(), wx.ID_ANY, u"Blacklist virtual components", wx.DefaultPosition, wx.DefaultSize, 0 )
self.blacklistVirtualCheckbox.SetValue(True)
blacklistSizer.Add( self.blacklistVirtualCheckbox, 0, wx.ALL, 5 )
self.blacklistEmptyValCheckbox = wx.CheckBox( blacklistSizer.GetStaticBox(), wx.ID_ANY, u"Blacklist components with empty value", wx.DefaultPosition, wx.DefaultSize, 0 )
blacklistSizer.Add( self.blacklistEmptyValCheckbox, 0, wx.ALL, 5 )
bSizer32.Add( blacklistSizer, 1, wx.ALL|wx.EXPAND|wx.TOP, 5 )
self.SetSizer( bSizer32 )
self.Layout()
bSizer32.Fit( self )
# Connect Events
self.Bind( wx.EVT_SIZE, self.OnSize )
self.m_btnNameHint.Bind( wx.EVT_BUTTON, self.OnNameFormatHintClick )
self.m_btnSortUp.Bind( wx.EVT_BUTTON, self.OnComponentSortOrderUp )
self.m_btnSortDown.Bind( wx.EVT_BUTTON, self.OnComponentSortOrderDown )
self.m_btnSortAdd.Bind( wx.EVT_BUTTON, self.OnComponentSortOrderAdd )
self.m_btnSortRemove.Bind( wx.EVT_BUTTON, self.OnComponentSortOrderRemove )
self.m_btnBlacklistAdd.Bind( wx.EVT_BUTTON, self.OnComponentBlacklistAdd )
self.m_btnBlacklistRemove.Bind( wx.EVT_BUTTON, self.OnComponentBlacklistRemove )
def __del__( self ):
pass
# Virtual event handlers, override them in your derived class
def OnSize( self, event ):
event.Skip()
def OnNameFormatHintClick( self, event ):
event.Skip()
def OnComponentSortOrderUp( self, event ):
event.Skip()
def OnComponentSortOrderDown( self, event ):
event.Skip()
def OnComponentSortOrderAdd( self, event ):
event.Skip()
def OnComponentSortOrderRemove( self, event ):
event.Skip()
def OnComponentBlacklistAdd( self, event ):
event.Skip()
def OnComponentBlacklistRemove( self, event ):
event.Skip()
###########################################################################
## Class FieldsPanelBase
###########################################################################
class FieldsPanelBase ( wx.Panel ):
def __init__( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( -1,-1 ), style = wx.TAB_TRAVERSAL, name = wx.EmptyString ):
wx.Panel.__init__ ( self, parent, id = id, pos = pos, size = size, style = style, name = name )
bSizer42 = wx.BoxSizer( wx.VERTICAL )
sbSizer7 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"Extra data file" ), wx.VERTICAL )
self.extraDataFilePicker = wx.FilePickerCtrl( sbSizer7.GetStaticBox(), wx.ID_ANY, wx.EmptyString, u"Select a file", u"Netlist and xml files (*.net; *.xml)|*.net;*.xml", wx.DefaultPosition, wx.DefaultSize, wx.FLP_DEFAULT_STYLE|wx.FLP_FILE_MUST_EXIST|wx.FLP_OPEN|wx.FLP_SMALL|wx.FLP_USE_TEXTCTRL|wx.BORDER_SIMPLE )
sbSizer7.Add( self.extraDataFilePicker, 0, wx.EXPAND|wx.BOTTOM|wx.RIGHT|wx.LEFT, 5 )
bSizer42.Add( sbSizer7, 0, wx.ALL|wx.EXPAND, 5 )
fieldsSizer = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"Fields" ), wx.VERTICAL )
bSizer4 = wx.BoxSizer( wx.HORIZONTAL )
self.fieldsGrid = wx.grid.Grid( fieldsSizer.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 )
# Grid
self.fieldsGrid.CreateGrid( 2, 3 )
self.fieldsGrid.EnableEditing( True )
self.fieldsGrid.EnableGridLines( True )
self.fieldsGrid.EnableDragGridSize( False )
self.fieldsGrid.SetMargins( 0, 0 )
# Columns
self.fieldsGrid.AutoSizeColumns()
self.fieldsGrid.EnableDragColMove( False )
self.fieldsGrid.EnableDragColSize( True )
self.fieldsGrid.SetColLabelValue( 0, u"Show" )
self.fieldsGrid.SetColLabelValue( 1, u"Group" )
self.fieldsGrid.SetColLabelValue( 2, u"Name" )
self.fieldsGrid.SetColLabelSize( 30 )
self.fieldsGrid.SetColLabelAlignment( wx.ALIGN_CENTER, wx.ALIGN_CENTER )
# Rows
self.fieldsGrid.EnableDragRowSize( False )
self.fieldsGrid.SetRowLabelSize( 0 )
self.fieldsGrid.SetRowLabelAlignment( wx.ALIGN_CENTER, wx.ALIGN_CENTER )
# Label Appearance
# Cell Defaults
self.fieldsGrid.SetDefaultCellAlignment( wx.ALIGN_CENTER, wx.ALIGN_TOP )
bSizer4.Add( self.fieldsGrid, 1, wx.ALL|wx.EXPAND, 5 )
bSizer5 = wx.BoxSizer( wx.VERTICAL )
self.m_btnUp = wx.Button( fieldsSizer.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT )
self.m_btnUp.SetMinSize( wx.Size( 30,30 ) )
bSizer5.Add( self.m_btnUp, 0, wx.ALL, 5 )
self.m_btnDown = wx.Button( fieldsSizer.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT )
self.m_btnDown.SetMinSize( wx.Size( 30,30 ) )
bSizer5.Add( self.m_btnDown, 0, wx.ALL, 5 )
bSizer4.Add( bSizer5, 0, 0, 5 )
fieldsSizer.Add( bSizer4, 1, wx.EXPAND, 5 )
self.normalizeCaseCheckbox = wx.CheckBox( fieldsSizer.GetStaticBox(), wx.ID_ANY, u"Normalize field name case", wx.DefaultPosition, wx.DefaultSize, 0 )
fieldsSizer.Add( self.normalizeCaseCheckbox, 0, wx.ALL|wx.EXPAND, 5 )
bSizer42.Add( fieldsSizer, 2, wx.ALL|wx.EXPAND, 5 )
sbSizer32 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"Board variant" ), wx.VERTICAL )
self.m_staticText5 = wx.StaticText( sbSizer32.GetStaticBox(), wx.ID_ANY, u"Board variant field name", wx.DefaultPosition, wx.DefaultSize, 0 )
self.m_staticText5.Wrap( -1 )
sbSizer32.Add( self.m_staticText5, 0, wx.ALL, 5 )
boardVariantFieldBoxChoices = []
self.boardVariantFieldBox = wx.ComboBox( sbSizer32.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, boardVariantFieldBoxChoices, wx.CB_READONLY|wx.CB_SORT|wx.BORDER_SIMPLE )
sbSizer32.Add( self.boardVariantFieldBox, 0, wx.ALL|wx.EXPAND, 5 )
bSizer17 = wx.BoxSizer( wx.HORIZONTAL )
bSizer18 = wx.BoxSizer( wx.VERTICAL )
self.m_staticText6 = wx.StaticText( sbSizer32.GetStaticBox(), wx.ID_ANY, u"Whitelist", wx.DefaultPosition, wx.DefaultSize, 0 )
self.m_staticText6.Wrap( -1 )
bSizer18.Add( self.m_staticText6, 0, wx.ALL, 5 )
boardVariantWhitelistChoices = []
self.boardVariantWhitelist = wx.CheckListBox( sbSizer32.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, boardVariantWhitelistChoices, 0|wx.BORDER_SIMPLE )
bSizer18.Add( self.boardVariantWhitelist, 1, wx.ALL|wx.EXPAND, 5 )
bSizer17.Add( bSizer18, 1, wx.EXPAND, 5 )
bSizer19 = wx.BoxSizer( wx.VERTICAL )
self.m_staticText7 = wx.StaticText( sbSizer32.GetStaticBox(), wx.ID_ANY, u"Blacklist", wx.DefaultPosition, wx.DefaultSize, 0 )
self.m_staticText7.Wrap( -1 )
bSizer19.Add( self.m_staticText7, 0, wx.ALL, 5 )
boardVariantBlacklistChoices = []
self.boardVariantBlacklist = wx.CheckListBox( sbSizer32.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, boardVariantBlacklistChoices, 0|wx.BORDER_SIMPLE )
bSizer19.Add( self.boardVariantBlacklist, 1, wx.ALL|wx.EXPAND, 5 )
bSizer17.Add( bSizer19, 1, wx.EXPAND, 5 )
sbSizer32.Add( bSizer17, 1, wx.EXPAND, 5 )
bSizer42.Add( sbSizer32, 3, wx.ALL|wx.EXPAND, 5 )
sbSizer8 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"DNP field name" ), wx.VERTICAL )
self.m_staticText4 = wx.StaticText( sbSizer8.GetStaticBox(), wx.ID_ANY, u"Components with this field not empty will be ignored", wx.DefaultPosition, wx.DefaultSize, 0 )
self.m_staticText4.Wrap( -1 )
sbSizer8.Add( self.m_staticText4, 0, wx.ALL, 5 )
dnpFieldBoxChoices = []
self.dnpFieldBox = wx.ComboBox( sbSizer8.GetStaticBox(), wx.ID_ANY, u"-None-", wx.DefaultPosition, wx.DefaultSize, dnpFieldBoxChoices, wx.CB_READONLY|wx.CB_SORT|wx.BORDER_NONE )
sbSizer8.Add( self.dnpFieldBox, 0, wx.ALL|wx.EXPAND, 5 )
bSizer42.Add( sbSizer8, 0, wx.ALL|wx.EXPAND, 5 )
self.SetSizer( bSizer42 )
self.Layout()
bSizer42.Fit( self )
# Connect Events
self.Bind( wx.EVT_SIZE, self.OnSize )
self.extraDataFilePicker.Bind( wx.EVT_FILEPICKER_CHANGED, self.OnExtraDataFileChanged )
self.fieldsGrid.Bind( wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnGridCellClicked )
self.m_btnUp.Bind( wx.EVT_BUTTON, self.OnFieldsUp )
self.m_btnDown.Bind( wx.EVT_BUTTON, self.OnFieldsDown )
self.normalizeCaseCheckbox.Bind( wx.EVT_CHECKBOX, self.OnNetlistFileChanged )
self.boardVariantFieldBox.Bind( wx.EVT_COMBOBOX, self.OnBoardVariantFieldChange )
def __del__( self ):
pass
# Virtual event handlers, override them in your derived class
def OnSize( self, event ):
event.Skip()
def OnExtraDataFileChanged( self, event ):
event.Skip()
def OnGridCellClicked( self, event ):
event.Skip()
def OnFieldsUp( self, event ):
event.Skip()
def OnFieldsDown( self, event ):
event.Skip()
def OnNetlistFileChanged( self, event ):
event.Skip()
def OnBoardVariantFieldChange( self, event ):
event.Skip()

View File

@ -0,0 +1,390 @@
import os
import re
import wx
import wx.grid
from . import dialog_base
if hasattr(wx, "GetLibraryVersionInfo"):
WX_VERSION = wx.GetLibraryVersionInfo() # type: wx.VersionInfo
WX_VERSION = (WX_VERSION.Major, WX_VERSION.Minor, WX_VERSION.Micro)
else:
# old kicad used this (exact version doesnt matter)
WX_VERSION = (3, 0, 2)
def pop_error(msg):
wx.MessageBox(msg, 'Error', wx.OK | wx.ICON_ERROR)
def get_btn_bitmap(bitmap):
path = os.path.join(os.path.dirname(__file__), "bitmaps", bitmap)
png = wx.Bitmap(path, wx.BITMAP_TYPE_PNG)
if WX_VERSION >= (3, 1, 6):
return wx.BitmapBundle(png)
else:
return png
class SettingsDialog(dialog_base.SettingsDialogBase):
def __init__(self, extra_data_func, extra_data_wildcard, config_save_func,
file_name_format_hint, version):
dialog_base.SettingsDialogBase.__init__(self, None)
self.panel = SettingsDialogPanel(
self, extra_data_func, extra_data_wildcard, config_save_func,
file_name_format_hint)
best_size = self.panel.BestSize
# hack for some gtk themes that incorrectly calculate best size
best_size.IncBy(dx=0, dy=30)
self.SetClientSize(best_size)
self.SetTitle('InteractiveHtmlBom %s' % version)
# hack for new wxFormBuilder generating code incompatible with old wxPython
# noinspection PyMethodOverriding
def SetSizeHints(self, sz1, sz2):
try:
# wxPython 4
super(SettingsDialog, self).SetSizeHints(sz1, sz2)
except TypeError:
# wxPython 3
self.SetSizeHintsSz(sz1, sz2)
def set_extra_data_path(self, extra_data_file):
self.panel.fields.extraDataFilePicker.Path = extra_data_file
self.panel.fields.OnExtraDataFileChanged(None)
# Implementing settings_dialog
class SettingsDialogPanel(dialog_base.SettingsDialogPanel):
def __init__(self, parent, extra_data_func, extra_data_wildcard,
config_save_func, file_name_format_hint):
self.config_save_func = config_save_func
dialog_base.SettingsDialogPanel.__init__(self, parent)
self.general = GeneralSettingsPanel(self.notebook,
file_name_format_hint)
self.html = HtmlSettingsPanel(self.notebook)
self.fields = FieldsPanel(self.notebook, extra_data_func,
extra_data_wildcard)
self.notebook.AddPage(self.general, "General")
self.notebook.AddPage(self.html, "Html defaults")
self.notebook.AddPage(self.fields, "Fields")
self.save_menu = wx.Menu()
self.save_locally = self.save_menu.Append(
wx.ID_ANY, u"Locally", wx.EmptyString, wx.ITEM_NORMAL)
self.save_globally = self.save_menu.Append(
wx.ID_ANY, u"Globally", wx.EmptyString, wx.ITEM_NORMAL)
self.Bind(
wx.EVT_MENU, self.OnSaveLocally, id=self.save_locally.GetId())
self.Bind(
wx.EVT_MENU, self.OnSaveGlobally, id=self.save_globally.GetId())
def OnExit(self, event):
self.GetParent().EndModal(wx.ID_CANCEL)
def OnGenerateBom(self, event):
self.GetParent().EndModal(wx.ID_OK)
def finish_init(self):
self.html.OnBoardRotationSlider(None)
def OnSave(self, event):
# type: (wx.CommandEvent) -> None
pos = wx.Point(0, event.GetEventObject().GetSize().y)
self.saveSettingsBtn.PopupMenu(self.save_menu, pos)
def OnSaveGlobally(self, event):
self.config_save_func(self)
def OnSaveLocally(self, event):
self.config_save_func(self, locally=True)
# Implementing HtmlSettingsPanelBase
class HtmlSettingsPanel(dialog_base.HtmlSettingsPanelBase):
def __init__(self, parent):
dialog_base.HtmlSettingsPanelBase.__init__(self, parent)
# Handlers for HtmlSettingsPanelBase events.
def OnBoardRotationSlider(self, event):
degrees = self.boardRotationSlider.Value * 5
self.rotationDegreeLabel.LabelText = u"{}\u00B0".format(degrees)
# Implementing GeneralSettingsPanelBase
class GeneralSettingsPanel(dialog_base.GeneralSettingsPanelBase):
def __init__(self, parent, file_name_format_hint):
dialog_base.GeneralSettingsPanelBase.__init__(self, parent)
self.file_name_format_hint = file_name_format_hint
bmp_arrow_up = get_btn_bitmap("btn-arrow-up.png")
bmp_arrow_down = get_btn_bitmap("btn-arrow-down.png")
bmp_plus = get_btn_bitmap("btn-plus.png")
bmp_minus = get_btn_bitmap("btn-minus.png")
bmp_question = get_btn_bitmap("btn-question.png")
self.m_btnSortUp.SetBitmap(bmp_arrow_up)
self.m_btnSortDown.SetBitmap(bmp_arrow_down)
self.m_btnSortAdd.SetBitmap(bmp_plus)
self.m_btnSortRemove.SetBitmap(bmp_minus)
self.m_btnNameHint.SetBitmap(bmp_question)
self.m_btnBlacklistAdd.SetBitmap(bmp_plus)
self.m_btnBlacklistRemove.SetBitmap(bmp_minus)
self.Layout()
# Handlers for GeneralSettingsPanelBase events.
def OnComponentSortOrderUp(self, event):
selection = self.componentSortOrderBox.Selection
if selection != wx.NOT_FOUND and selection > 0:
item = self.componentSortOrderBox.GetString(selection)
self.componentSortOrderBox.Delete(selection)
self.componentSortOrderBox.Insert(item, selection - 1)
self.componentSortOrderBox.SetSelection(selection - 1)
def OnComponentSortOrderDown(self, event):
selection = self.componentSortOrderBox.Selection
size = self.componentSortOrderBox.Count
if selection != wx.NOT_FOUND and selection < size - 1:
item = self.componentSortOrderBox.GetString(selection)
self.componentSortOrderBox.Delete(selection)
self.componentSortOrderBox.Insert(item, selection + 1)
self.componentSortOrderBox.SetSelection(selection + 1)
def OnComponentSortOrderAdd(self, event):
item = wx.GetTextFromUser(
"Characters other than A-Z will be ignored.",
"Add sort order item")
item = re.sub('[^A-Z]', '', item.upper())
if item == '':
return
found = self.componentSortOrderBox.FindString(item)
if found != wx.NOT_FOUND:
self.componentSortOrderBox.SetSelection(found)
return
self.componentSortOrderBox.Append(item)
self.componentSortOrderBox.SetSelection(
self.componentSortOrderBox.Count - 1)
def OnComponentSortOrderRemove(self, event):
selection = self.componentSortOrderBox.Selection
if selection != wx.NOT_FOUND:
item = self.componentSortOrderBox.GetString(selection)
if item == '~':
pop_error("You can not delete '~' item")
return
self.componentSortOrderBox.Delete(selection)
if self.componentSortOrderBox.Count > 0:
self.componentSortOrderBox.SetSelection(max(selection - 1, 0))
def OnComponentBlacklistAdd(self, event):
item = wx.GetTextFromUser(
"Characters other than A-Z 0-9 and * will be ignored.",
"Add blacklist item")
item = re.sub('[^A-Z0-9*]', '', item.upper())
if item == '':
return
found = self.blacklistBox.FindString(item)
if found != wx.NOT_FOUND:
self.blacklistBox.SetSelection(found)
return
self.blacklistBox.Append(item)
self.blacklistBox.SetSelection(self.blacklistBox.Count - 1)
def OnComponentBlacklistRemove(self, event):
selection = self.blacklistBox.Selection
if selection != wx.NOT_FOUND:
self.blacklistBox.Delete(selection)
if self.blacklistBox.Count > 0:
self.blacklistBox.SetSelection(max(selection - 1, 0))
def OnNameFormatHintClick(self, event):
wx.MessageBox(self.file_name_format_hint, 'File name format help',
style=wx.ICON_NONE | wx.OK)
def OnSize(self, event):
# Trick the listCheckBox best size calculations
tmp = self.componentSortOrderBox.GetStrings()
self.componentSortOrderBox.SetItems([])
self.Layout()
self.componentSortOrderBox.SetItems(tmp)
# Implementing FieldsPanelBase
class FieldsPanel(dialog_base.FieldsPanelBase):
NONE_STRING = '<none>'
FIELDS_GRID_COLUMNS = 3
def __init__(self, parent, extra_data_func, extra_data_wildcard):
dialog_base.FieldsPanelBase.__init__(self, parent)
self.extra_data_func = extra_data_func
self.extra_field_data = None
self.m_btnUp.SetBitmap(get_btn_bitmap("btn-arrow-up.png"))
self.m_btnDown.SetBitmap(get_btn_bitmap("btn-arrow-down.png"))
self.set_file_picker_wildcard(extra_data_wildcard)
self._setFieldsList([])
for i in range(2):
box = self.GetTextExtent(self.fieldsGrid.GetColLabelValue(i))
if hasattr(box, "x"):
width = box.x
else:
width = box[0]
width = int(width * 1.1 + 5)
self.fieldsGrid.SetColMinimalWidth(i, width)
self.fieldsGrid.SetColSize(i, width)
self.Layout()
def set_file_picker_wildcard(self, extra_data_wildcard):
if extra_data_wildcard is None:
self.extraDataFilePicker.Disable()
return
# wxFilePickerCtrl doesn't support changing wildcard at runtime
# so we have to replace it
picker_parent = self.extraDataFilePicker.GetParent()
new_picker = wx.FilePickerCtrl(
picker_parent, wx.ID_ANY, wx.EmptyString,
u"Select a file",
extra_data_wildcard,
wx.DefaultPosition, wx.DefaultSize,
(wx.FLP_DEFAULT_STYLE | wx.FLP_FILE_MUST_EXIST | wx.FLP_OPEN |
wx.FLP_SMALL | wx.FLP_USE_TEXTCTRL | wx.BORDER_SIMPLE))
self.GetSizer().Replace(self.extraDataFilePicker, new_picker,
recursive=True)
self.extraDataFilePicker.Destroy()
self.extraDataFilePicker = new_picker
self.Layout()
def _swapRows(self, a, b):
for i in range(self.FIELDS_GRID_COLUMNS):
va = self.fieldsGrid.GetCellValue(a, i)
vb = self.fieldsGrid.GetCellValue(b, i)
self.fieldsGrid.SetCellValue(a, i, vb)
self.fieldsGrid.SetCellValue(b, i, va)
# Handlers for FieldsPanelBase events.
def OnGridCellClicked(self, event):
self.fieldsGrid.ClearSelection()
self.fieldsGrid.SelectRow(event.Row)
if event.Col < 2:
# toggle checkbox
val = self.fieldsGrid.GetCellValue(event.Row, event.Col)
val = "" if val else "1"
self.fieldsGrid.SetCellValue(event.Row, event.Col, val)
# group shouldn't be enabled without show
if event.Col == 0 and val == "":
self.fieldsGrid.SetCellValue(event.Row, 1, val)
if event.Col == 1 and val == "1":
self.fieldsGrid.SetCellValue(event.Row, 0, val)
def OnFieldsUp(self, event):
selection = self.fieldsGrid.SelectedRows
if len(selection) == 1 and selection[0] > 0:
self._swapRows(selection[0], selection[0] - 1)
self.fieldsGrid.ClearSelection()
self.fieldsGrid.SelectRow(selection[0] - 1)
def OnFieldsDown(self, event):
selection = self.fieldsGrid.SelectedRows
size = self.fieldsGrid.NumberRows
if len(selection) == 1 and selection[0] < size - 1:
self._swapRows(selection[0], selection[0] + 1)
self.fieldsGrid.ClearSelection()
self.fieldsGrid.SelectRow(selection[0] + 1)
def _setFieldsList(self, fields):
if self.fieldsGrid.NumberRows:
self.fieldsGrid.DeleteRows(0, self.fieldsGrid.NumberRows)
self.fieldsGrid.AppendRows(len(fields))
row = 0
for f in fields:
self.fieldsGrid.SetCellValue(row, 0, "1")
self.fieldsGrid.SetCellValue(row, 1, "1")
self.fieldsGrid.SetCellRenderer(
row, 0, wx.grid.GridCellBoolRenderer())
self.fieldsGrid.SetCellRenderer(
row, 1, wx.grid.GridCellBoolRenderer())
self.fieldsGrid.SetCellValue(row, 2, f)
self.fieldsGrid.SetCellAlignment(
row, 2, wx.ALIGN_LEFT, wx.ALIGN_TOP)
self.fieldsGrid.SetReadOnly(row, 2)
row += 1
def OnExtraDataFileChanged(self, event):
extra_data_file = self.extraDataFilePicker.Path
if not os.path.isfile(extra_data_file):
return
self.extra_field_data = None
try:
self.extra_field_data = self.extra_data_func(
extra_data_file, self.normalizeCaseCheckbox.Value)
except Exception as e:
pop_error(
"Failed to parse file %s\n\n%s" % (extra_data_file, e))
self.extraDataFilePicker.Path = ''
if self.extra_field_data is not None:
field_list = list(self.extra_field_data[0])
self._setFieldsList(["Value", "Footprint"] + field_list)
field_list.append(self.NONE_STRING)
self.boardVariantFieldBox.SetItems(field_list)
self.boardVariantFieldBox.SetStringSelection(self.NONE_STRING)
self.boardVariantWhitelist.Clear()
self.boardVariantBlacklist.Clear()
self.dnpFieldBox.SetItems(field_list)
self.dnpFieldBox.SetStringSelection(self.NONE_STRING)
def OnBoardVariantFieldChange(self, event):
selection = self.boardVariantFieldBox.Value
if not selection or selection == self.NONE_STRING \
or self.extra_field_data is None:
self.boardVariantWhitelist.Clear()
self.boardVariantBlacklist.Clear()
return
variant_set = set()
for _, field_dict in self.extra_field_data[1].items():
if selection in field_dict:
variant_set.add(field_dict[selection])
self.boardVariantWhitelist.SetItems(list(variant_set))
self.boardVariantBlacklist.SetItems(list(variant_set))
def OnSize(self, event):
self.Layout()
g = self.fieldsGrid
g.SetColSize(
2, g.GetClientSize().x - g.GetColSize(0) - g.GetColSize(1) - 30)
def GetShowFields(self):
result = []
for row in range(self.fieldsGrid.NumberRows):
if self.fieldsGrid.GetCellValue(row, 0) == "1":
result.append(self.fieldsGrid.GetCellValue(row, 2))
return result
def GetGroupFields(self):
result = []
for row in range(self.fieldsGrid.NumberRows):
if self.fieldsGrid.GetCellValue(row, 1) == "1":
result.append(self.fieldsGrid.GetCellValue(row, 2))
return result
def SetCheckedFields(self, show, group):
group = [s for s in group if s in show]
current = []
for row in range(self.fieldsGrid.NumberRows):
current.append(self.fieldsGrid.GetCellValue(row, 2))
new = [s for s in current if s not in show]
self._setFieldsList(show + new)
for row in range(self.fieldsGrid.NumberRows):
field = self.fieldsGrid.GetCellValue(row, 2)
self.fieldsGrid.SetCellValue(row, 0, "1" if field in show else "")
self.fieldsGrid.SetCellValue(row, 1, "1" if field in group else "")

View File

@ -0,0 +1,41 @@
import os
def get_parser_by_extension(file_name, config, logger):
ext = os.path.splitext(file_name)[1]
if ext == '.kicad_pcb':
return get_kicad_parser(file_name, config, logger)
elif ext == '.json':
""".json file may be from EasyEDA or a generic json format"""
import io
import json
with io.open(file_name, 'r', encoding='utf-8') as f:
obj = json.load(f)
if 'pcbdata' in obj:
return get_generic_json_parser(file_name, config, logger)
else:
return get_easyeda_parser(file_name, config, logger)
elif ext in ['.fbrd', '.brd']:
return get_fusion_eagle_parser(file_name, config, logger)
else:
return None
def get_kicad_parser(file_name, config, logger, board=None):
from .kicad import PcbnewParser
return PcbnewParser(file_name, config, logger, board)
def get_easyeda_parser(file_name, config, logger):
from .easyeda import EasyEdaParser
return EasyEdaParser(file_name, config, logger)
def get_generic_json_parser(file_name, config, logger):
from .genericjson import GenericJsonParser
return GenericJsonParser(file_name, config, logger)
def get_fusion_eagle_parser(file_name, config, logger):
from .fusion_eagle import FusionEagleParser
return FusionEagleParser(file_name, config, logger)

View File

@ -0,0 +1,248 @@
import math
from .svgpath import parse_path
class EcadParser(object):
def __init__(self, file_name, config, logger):
"""
:param file_name: path to file that should be parsed.
:param config: Config instance
:param logger: logging object.
"""
self.file_name = file_name
self.config = config
self.logger = logger
def parse(self):
"""
Abstract method that should be overridden in implementations.
Performs all the parsing and returns a tuple of
(pcbdata, components)
pcbdata is described in DATAFORMAT.md
components is list of Component objects
:return:
"""
pass
@staticmethod
def normalize_field_names(data):
field_map = {f.lower(): f for f in reversed(data[0])}
def remap(ref_fields):
return {field_map[f.lower()]: v for (f, v) in
sorted(ref_fields.items(), reverse=True)}
field_data = {r: remap(d) for (r, d) in data[1].items()}
return field_map.values(), field_data
def get_extra_field_data(self, file_name):
"""
Abstract method that may be overridden in implementations that support
extra field data.
:return: tuple of the format
(
[field_name1, field_name2,... ],
{
ref1: {
field_name1: field_value1,
field_name2: field_value2,
...
],
ref2: ...
}
)
"""
return [], {}
def parse_extra_data(self, file_name, normalize_case):
"""
Parses the file and returns extra field data.
:param file_name: path to file containing extra data
:param normalize_case: if true, normalize case so that
"mpn", "Mpn", "MPN" fields are combined
:return:
"""
data = self.get_extra_field_data(file_name)
if normalize_case:
data = self.normalize_field_names(data)
return sorted(data[0]), data[1]
def latest_extra_data(self, extra_dirs=None):
"""
Abstract method that may be overridden in implementations that support
extra field data.
:param extra_dirs: List of extra directories to search.
:return: File name of most recent file with extra field data.
"""
return None
def extra_data_file_filter(self):
"""
Abstract method that may be overridden in implementations that support
extra field data.
:return: File open dialog filter string, eg:
"Netlist and xml files (*.net; *.xml)|*.net;*.xml"
"""
return None
def add_drawing_bounding_box(self, drawing, bbox):
# type: (dict, BoundingBox) -> None
def add_segment():
bbox.add_segment(drawing['start'][0], drawing['start'][1],
drawing['end'][0], drawing['end'][1],
drawing['width'] / 2)
def add_circle():
bbox.add_circle(drawing['start'][0], drawing['start'][1],
drawing['radius'] + drawing['width'] / 2)
def add_svgpath():
width = drawing.get('width', 0)
bbox.add_svgpath(drawing['svgpath'], width, self.logger)
def add_polygon():
if 'polygons' not in drawing:
add_svgpath()
return
polygon = drawing['polygons'][0]
for point in polygon:
bbox.add_point(point[0], point[1])
def add_arc():
if 'svgpath' in drawing:
add_svgpath()
else:
width = drawing.get('width', 0)
xc, yc = drawing['start'][:2]
a1 = drawing['startangle']
a2 = drawing['endangle']
r = drawing['radius']
x1 = xc + math.cos(math.radians(a1))
y1 = xc + math.sin(math.radians(a1))
x2 = xc + math.cos(math.radians(a2))
y2 = xc + math.sin(math.radians(a2))
da = a2 - a1 if a2 > a1 else a2 + 360 - a1
la = 1 if da > 180 else 0
svgpath = 'M %s %s A %s %s 0 %s 1 %s %s' % \
(x1, y1, r, r, la, x2, y2)
bbox.add_svgpath(svgpath, width, self.logger)
{
'segment': add_segment,
'rect': add_segment, # bbox of a rect and segment are the same
'circle': add_circle,
'arc': add_arc,
'polygon': add_polygon,
'text': lambda: None, # text is not really needed for bounding box
}.get(drawing['type'])()
class Component(object):
"""Simple data object to store component data needed for bom table."""
def __init__(self, ref, val, footprint, layer, attr=None, extra_fields={}):
self.ref = ref
self.val = val
self.footprint = footprint
self.layer = layer
self.attr = attr
self.extra_fields = extra_fields
class BoundingBox(object):
"""Geometry util to calculate and combine bounding box of simple shapes."""
def __init__(self):
self._x0 = None
self._y0 = None
self._x1 = None
self._y1 = None
def to_dict(self):
# type: () -> dict
return {
"minx": self._x0,
"miny": self._y0,
"maxx": self._x1,
"maxy": self._y1,
}
def to_component_dict(self):
# type: () -> dict
return {
"pos": [self._x0, self._y0],
"relpos": [0, 0],
"size": [self._x1 - self._x0, self._y1 - self._y0],
"angle": 0,
}
def add(self, other):
"""Add another bounding box.
:type other: BoundingBox
"""
if other._x0 is not None:
self.add_point(other._x0, other._y0)
self.add_point(other._x1, other._y1)
return self
@staticmethod
def _rotate(x, y, rx, ry, angle):
sin = math.sin(math.radians(angle))
cos = math.cos(math.radians(angle))
new_x = rx + (x - rx) * cos - (y - ry) * sin
new_y = ry + (x - rx) * sin + (y - ry) * cos
return new_x, new_y
def add_point(self, x, y, rx=0, ry=0, angle=0):
x, y = self._rotate(x, y, rx, ry, angle)
if self._x0 is None:
self._x0 = x
self._y0 = y
self._x1 = x
self._y1 = y
else:
self._x0 = min(self._x0, x)
self._y0 = min(self._y0, y)
self._x1 = max(self._x1, x)
self._y1 = max(self._y1, y)
return self
def add_segment(self, x0, y0, x1, y1, r):
self.add_circle(x0, y0, r)
self.add_circle(x1, y1, r)
return self
def add_rectangle(self, x, y, w, h, angle=0):
self.add_point(x - w / 2, y - h / 2, x, y, angle)
self.add_point(x + w / 2, y - h / 2, x, y, angle)
self.add_point(x - w / 2, y + h / 2, x, y, angle)
self.add_point(x + w / 2, y + h / 2, x, y, angle)
return self
def add_circle(self, x, y, r):
self.add_point(x - r, y)
self.add_point(x, y - r)
self.add_point(x + r, y)
self.add_point(x, y + r)
return self
def add_svgpath(self, svgpath, width, logger):
w = width / 2
for segment in parse_path(svgpath, logger):
x0, x1, y0, y1 = segment.bbox()
self.add_point(x0 - w, y0 - w)
self.add_point(x1 + w, y1 + w)
def pad(self, amount):
"""Add small padding to the box."""
if self._x0 is not None:
self._x0 -= amount
self._y0 -= amount
self._x1 += amount
self._y1 += amount
def initialized(self):
return self._x0 is not None

View File

@ -0,0 +1,462 @@
import io
import sys
from .common import EcadParser, Component, BoundingBox
if sys.version_info >= (3, 0):
string_types = str
else:
string_types = basestring # noqa F821: ignore undefined
class EasyEdaParser(EcadParser):
TOP_COPPER_LAYER = 1
BOT_COPPER_LAYER = 2
TOP_SILK_LAYER = 3
BOT_SILK_LAYER = 4
BOARD_OUTLINE_LAYER = 10
TOP_ASSEMBLY_LAYER = 13
BOT_ASSEMBLY_LAYER = 14
ALL_LAYERS = 11
def get_easyeda_pcb(self):
import json
with io.open(self.file_name, 'r', encoding='utf-8') as f:
return json.load(f)
@staticmethod
def tilda_split(s):
# type: (str) -> list
return s.split('~')
@staticmethod
def sharp_split(s):
# type: (str) -> list
return s.split('#@$')
def _verify(self, pcb):
"""Spot check the pcb object."""
if 'head' not in pcb:
self.logger.error('No head attribute.')
return False
head = pcb['head']
if len(head) < 2:
self.logger.error('Incorrect head attribute ' + pcb['head'])
return False
if head['docType'] != '3':
self.logger.error('Incorrect document type: ' + head['docType'])
return False
if 'canvas' not in pcb:
self.logger.error('No canvas attribute.')
return False
canvas = self.tilda_split(pcb['canvas'])
if len(canvas) < 18:
self.logger.error('Incorrect canvas attribute ' + pcb['canvas'])
return False
self.logger.info('EasyEDA editor version ' + head['editorVersion'])
return True
@staticmethod
def normalize(v):
if isinstance(v, string_types):
v = float(v)
return v
def parse_track(self, shape):
shape = self.tilda_split(shape)
assert len(shape) >= 5, 'Invalid track ' + str(shape)
width = self.normalize(shape[0])
layer = int(shape[1])
points = [self.normalize(v) for v in shape[3].split(' ')]
points_xy = [[points[i], points[i + 1]] for i in
range(0, len(points), 2)]
segments = [(points_xy[i], points_xy[i + 1]) for i in
range(len(points_xy) - 1)]
segments_json = []
for segment in segments:
segments_json.append({
"type": "segment",
"start": segment[0],
"end": segment[1],
"width": width,
})
return layer, segments_json
def parse_rect(self, shape):
shape = self.tilda_split(shape)
assert len(shape) >= 9, 'Invalid rect ' + str(shape)
x = self.normalize(shape[0])
y = self.normalize(shape[1])
width = self.normalize(shape[2])
height = self.normalize(shape[3])
layer = int(shape[4])
fill = shape[8]
if fill == "none":
thickness = self.normalize(shape[7])
return layer, [{
"type": "rect",
"start": [x, y],
"end": [x + width, y + height],
"width": thickness,
}]
else:
return layer, [{
"type": "polygon",
"pos": [x, y],
"angle": 0,
"polygons": [
[[0, 0], [width, 0], [width, height], [0, height]]
]
}]
def parse_circle(self, shape):
shape = self.tilda_split(shape)
assert len(shape) >= 6, 'Invalid circle ' + str(shape)
cx = self.normalize(shape[0])
cy = self.normalize(shape[1])
r = self.normalize(shape[2])
width = self.normalize(shape[3])
layer = int(shape[4])
return layer, [{
"type": "circle",
"start": [cx, cy],
"radius": r,
"width": width
}]
def parse_solid_region(self, shape):
shape = self.tilda_split(shape)
assert len(shape) >= 5, 'Invalid solid region ' + str(shape)
layer = int(shape[0])
svgpath = shape[2]
return layer, [{
"type": "polygon",
"svgpath": svgpath,
}]
def parse_text(self, shape):
shape = self.tilda_split(shape)
assert len(shape) >= 12, 'Invalid text ' + str(shape)
text_type = shape[0]
stroke_width = self.normalize(shape[3])
layer = int(shape[6])
text = shape[9]
svgpath = shape[10]
hide = shape[11]
return layer, [{
"type": "text",
"text": text,
"thickness": stroke_width,
"attr": [],
"svgpath": svgpath,
"hide": hide,
"text_type": text_type,
}]
def parse_arc(self, shape):
shape = self.tilda_split(shape)
assert len(shape) >= 6, 'Invalid arc ' + str(shape)
width = self.normalize(shape[0])
layer = int(shape[1])
svgpath = shape[3]
return layer, [{
"type": "arc",
"svgpath": svgpath,
"width": width
}]
def parse_hole(self, shape):
shape = self.tilda_split(shape)
assert len(shape) >= 4, 'Invalid hole ' + str(shape)
cx = self.normalize(shape[0])
cy = self.normalize(shape[1])
radius = self.normalize(shape[2])
return self.BOARD_OUTLINE_LAYER, [{
"type": "circle",
"start": [cx, cy],
"radius": radius,
"width": 0.1, # 1 mil
}]
def parse_pad(self, shape):
shape = self.tilda_split(shape)
assert len(shape) >= 15, 'Invalid pad ' + str(shape)
pad_shape = shape[0]
x = self.normalize(shape[1])
y = self.normalize(shape[2])
width = self.normalize(shape[3])
height = self.normalize(shape[4])
layer = int(shape[5])
number = shape[7]
hole_radius = self.normalize(shape[8])
if shape[9]:
points = [self.normalize(v) for v in shape[9].split(' ')]
else:
points = []
angle = int(shape[10])
hole_length = self.normalize(shape[12]) if shape[12] else 0
pad_layers = {
self.TOP_COPPER_LAYER: ['F'],
self.BOT_COPPER_LAYER: ['B'],
self.ALL_LAYERS: ['F', 'B']
}.get(layer)
pad_shape = {
"ELLIPSE": "circle",
"RECT": "rect",
"OVAL": "oval",
"POLYGON": "custom",
}.get(pad_shape)
pad_type = "smd" if len(pad_layers) == 1 else "th"
json = {
"layers": pad_layers,
"pos": [x, y],
"size": [width, height],
"angle": angle,
"shape": pad_shape,
"type": pad_type,
}
if number == '1':
json['pin1'] = 1
if pad_shape == "custom":
polygon = [(points[i], points[i + 1]) for i in
range(0, len(points), 2)]
# translate coordinates to be relative to footprint
polygon = [(p[0] - x, p[1] - y) for p in polygon]
json["polygons"] = [polygon]
json["angle"] = 0
if pad_type == "th":
if hole_length > 1e-6:
json["drillshape"] = "oblong"
json["drillsize"] = [hole_radius * 2, hole_length]
else:
json["drillshape"] = "circle"
json["drillsize"] = [hole_radius * 2, hole_radius * 2]
return layer, [{
"type": "pad",
"pad": json,
}]
@staticmethod
def add_pad_bounding_box(pad, bbox):
# type: (dict, BoundingBox) -> None
def add_circle():
bbox.add_circle(pad['pos'][0], pad['pos'][1], pad['size'][0] / 2)
def add_rect():
bbox.add_rectangle(pad['pos'][0], pad['pos'][1],
pad['size'][0], pad['size'][1],
pad['angle'])
def add_custom():
x = pad['pos'][0]
y = pad['pos'][1]
polygon = pad['polygons'][0]
for point in polygon:
bbox.add_point(x + point[0], y + point[1])
{
'circle': add_circle,
'rect': add_rect,
'oval': add_rect,
'custom': add_custom,
}.get(pad['shape'])()
def parse_lib(self, shape):
parts = self.sharp_split(shape)
head = self.tilda_split(parts[0])
inner_shapes, _, _ = self.parse_shapes(parts[1:])
x = self.normalize(head[0])
y = self.normalize(head[1])
attr = head[2]
fp_layer = int(head[6])
attr = attr.split('`')
if len(attr) % 2 != 0:
attr.pop()
attr = {attr[i]: attr[i + 1] for i in range(0, len(attr), 2)}
fp_layer = 'F' if fp_layer == self.TOP_COPPER_LAYER else 'B'
val = '??'
ref = '??'
footprint = attr.get('package', '??')
pads = []
copper_drawings = []
extra_drawings = []
bbox = BoundingBox()
for layer, shapes in inner_shapes.items():
for s in shapes:
if s["type"] == "pad":
pads.append(s["pad"])
continue
if s["type"] == "text":
if s["text_type"] == "N":
val = s["text"]
if s["text_type"] == "P":
ref = s["text"]
del s["text_type"]
if s["hide"]:
continue
if layer in [self.TOP_COPPER_LAYER, self.BOT_COPPER_LAYER]:
copper_drawings.append({
"layer": (
'F' if layer == self.TOP_COPPER_LAYER else 'B'),
"drawing": s,
})
elif layer in [self.TOP_SILK_LAYER,
self.BOT_SILK_LAYER,
self.TOP_ASSEMBLY_LAYER,
self.BOT_ASSEMBLY_LAYER,
self.BOARD_OUTLINE_LAYER]:
extra_drawings.append((layer, s))
for pad in pads:
self.add_pad_bounding_box(pad, bbox)
for drawing in copper_drawings:
self.add_drawing_bounding_box(drawing['drawing'], bbox)
for _, drawing in extra_drawings:
self.add_drawing_bounding_box(drawing, bbox)
bbox.pad(0.5) # pad by 5 mil
if not bbox.initialized():
# if bounding box is not calculated yet
# set it to 100x100 mil square
bbox.add_rectangle(x, y, 10, 10, 0)
footprint_json = {
"ref": ref,
"center": [x, y],
"bbox": bbox.to_component_dict(),
"pads": pads,
"drawings": copper_drawings,
"layer": fp_layer,
}
component = Component(ref, val, footprint, fp_layer)
return fp_layer, component, footprint_json, extra_drawings
def parse_shapes(self, shapes):
drawings = {}
footprints = []
components = []
for shape_str in shapes:
shape = shape_str.split('~', 1)
parse_func = {
'TRACK': self.parse_track,
'RECT': self.parse_rect,
'CIRCLE': self.parse_circle,
'SOLIDREGION': self.parse_solid_region,
'TEXT': self.parse_text,
'ARC': self.parse_arc,
'PAD': self.parse_pad,
'HOLE': self.parse_hole,
}.get(shape[0], None)
if parse_func:
layer, json_list = parse_func(shape[1])
drawings.setdefault(layer, []).extend(json_list)
if shape[0] == 'LIB':
layer, component, json, extras = self.parse_lib(shape[1])
for drawing_layer, drawing in extras:
drawings.setdefault(drawing_layer, []).append(drawing)
footprints.append(json)
components.append(component)
return drawings, footprints, components
def get_metadata(self, pcb):
if hasattr(pcb, 'metadata'):
return pcb.metadata
else:
import os
from datetime import datetime
pcb_file_name = os.path.basename(self.file_name)
title = os.path.splitext(pcb_file_name)[0]
file_mtime = os.path.getmtime(self.file_name)
file_date = datetime.fromtimestamp(file_mtime).strftime(
'%Y-%m-%d %H:%M:%S')
return {
"title": title,
"revision": "",
"company": "",
"date": file_date,
}
def parse(self):
pcb = self.get_easyeda_pcb()
if not self._verify(pcb):
self.logger.error(
'File ' + self.file_name +
' does not appear to be valid EasyEDA json file.')
return None, None
drawings, footprints, components = self.parse_shapes(pcb['shape'])
board_outline_bbox = BoundingBox()
for drawing in drawings.get(self.BOARD_OUTLINE_LAYER, []):
self.add_drawing_bounding_box(drawing, board_outline_bbox)
if board_outline_bbox.initialized():
bbox = board_outline_bbox.to_dict()
else:
# if nothing is drawn on outline layer then rely on EasyEDA bbox
x = self.normalize(pcb['BBox']['x'])
y = self.normalize(pcb['BBox']['y'])
bbox = {
"minx": x,
"miny": y,
"maxx": x + self.normalize(pcb['BBox']['width']),
"maxy": y + self.normalize(pcb['BBox']['height'])
}
pcbdata = {
"edges_bbox": bbox,
"edges": drawings.get(self.BOARD_OUTLINE_LAYER, []),
"drawings": {
"silkscreen": {
'F': drawings.get(self.TOP_SILK_LAYER, []),
'B': drawings.get(self.BOT_SILK_LAYER, []),
},
"fabrication": {
'F': drawings.get(self.TOP_ASSEMBLY_LAYER, []),
'B': drawings.get(self.BOT_ASSEMBLY_LAYER, []),
},
},
"footprints": footprints,
"metadata": self.get_metadata(pcb),
"bom": {},
"font_data": {}
}
if self.config.include_tracks:
def filter_tracks(drawing_list, drawing_type, keys):
result = []
for d in drawing_list:
if d["type"] == drawing_type:
r = {}
for key in keys:
r[key] = d[key]
result.append(r)
return result
pcbdata["tracks"] = {
'F': filter_tracks(drawings.get(self.TOP_COPPER_LAYER, []),
"segment", ["start", "end", "width"]),
'B': filter_tracks(drawings.get(self.BOT_COPPER_LAYER, []),
"segment", ["start", "end", "width"]),
}
# zones are not supported
pcbdata["zones"] = {'F': [], 'B': []}
return pcbdata, components

View File

@ -0,0 +1,862 @@
import io
import math
import os
import string
import zipfile
from datetime import datetime
from xml.etree import ElementTree
from .common import EcadParser, Component, BoundingBox
from .svgpath import Arc
from ..core.fontparser import FontParser
class FusionEagleParser(EcadParser):
TOP_COPPER_LAYER = '1'
BOT_COPPER_LAYER = '16'
TOP_PLACE_LAYER = '21'
BOT_PLACE_LAYER = '22'
TOP_NAMES_LAYER = '25'
BOT_NAMES_LAYER = '26'
DIMENSION_LAYER = '20'
TOP_DOCU_LAYER = '51'
BOT_DOCU_LAYER = '52'
def __init__(self, file_name, config, logger):
super(FusionEagleParser, self).__init__(file_name, config, logger)
self.config = config
self.font_parser = FontParser()
self.min_via_w = 1e-3
self.pcbdata = {
'drawings': {
'silkscreen': {
'F': [],
'B': []
},
'fabrication': {
'F': [],
'B': []
}
},
'edges': [],
'footprints': [],
'font_data': {}
}
self.components = []
def _parse_pad_nets(self, signals):
elements = {}
for signal in signals.iter('signal'):
net = signal.attrib['name']
for c in signal.iter('contactref'):
e = c.attrib['element']
if e not in elements:
elements[e] = {}
elements[e][c.attrib['pad']] = net
self.elements_pad_nets = elements
@staticmethod
def _radian(ux, uy, vx, vy):
dot = ux * vx + uy * vy
mod = math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy))
rad = math.acos(dot / mod)
if ux * vy - uy * vx < 0.0:
rad = -rad
return rad
def _curve_to_svgparams(self, el, x=0, y=0, angle=0):
_x1 = float(el.attrib['x1'])
_x2 = float(el.attrib['x2'])
_y1 = -float(el.attrib['y1'])
_y2 = -float(el.attrib['y2'])
dx1, dy1 = self._rotate(_x1, _y1, -angle)
dx2, dy2 = self._rotate(_x2, _y2, -angle)
x1, y1 = x + dx1, -y + dy1
x2, y2 = x + dx2, -y + dy2
chord = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
theta = float(el.attrib['curve'])
r = abs(0.5 * chord / math.sin(math.radians(theta) / 2))
la = 0 if abs(theta) < 180 else 1
sw = 0 if theta > 0 else 1
return {
'x1': x1,
'y1': y1,
'r': r,
'la': la,
'sw': sw,
'x2': x2,
'y2': y2
}
def _curve_to_svgpath(self, el, x=0, y=0, angle=0):
p = self._curve_to_svgparams(el, x, y, angle)
return 'M {x1} {y1} A {r} {r} 0 {la} {sw} {x2} {y2}'.format(**p)
@staticmethod
class Rot:
def __init__(self, rot_string):
if not rot_string:
self.mirrored = False
self.spin = False
self.angle = 0
return
self.mirrored = 'M' in rot_string
self.spin = 'S' in rot_string
self.angle = float(''.join(d for d in rot_string
if d in string.digits + '.'))
def __str__(self):
return "Mirrored: {0}, Spin: {1}, Angle: {2}".format(self.mirrored,
self.spin,
self.angle)
def _rectangle_vertices(self, el):
# Note: Eagle specifies a rectangle using opposing corners
# (x1, y1) = lower-left and (x2, y2) = upper-right) and *optionally*
# a rotation angle. The size of the rectangle is defined by the
# corners irrespective of rotation angle, and then it is rotated
# about its own center point.
_x1 = float(el.attrib['x1'])
_x2 = float(el.attrib['x2'])
_y1 = -float(el.attrib['y1'])
_y2 = -float(el.attrib['y2'])
# Center of rectangle
xc = (_x1 + _x2) / 2
yc = (_y1 + _y2) / 2
# Vertices of rectangle relative to its center, un-rotated
_dv_c = [
(_x1 - xc, _y1 - yc),
(_x2 - xc, _y1 - yc),
(_x2 - xc, _y2 - yc),
(_x1 - xc, _y2 - yc)
]
elr = self.Rot(el.get('rot'))
# Rotate the rectangle about its center
dv_c = [self._rotate(_x, _y, -elr.angle, elr.mirrored)
for (_x, _y) in _dv_c]
# Map vertices to position relative to component origin, un-rotated
return [(_x + xc, _y + yc) for (_x, _y) in dv_c]
def _add_drawing(self, el):
layer_dest = {
self.DIMENSION_LAYER: self.pcbdata['edges'],
self.TOP_PLACE_LAYER: self.pcbdata['drawings']['silkscreen']['F'],
self.BOT_PLACE_LAYER: self.pcbdata['drawings']['silkscreen']['B'],
self.TOP_NAMES_LAYER: self.pcbdata['drawings']['silkscreen']['F'],
self.BOT_NAMES_LAYER: self.pcbdata['drawings']['silkscreen']['B']
}
if ("layer" in el.attrib) and (el.attrib['layer'] in layer_dest):
dwg = None
if el.tag == 'wire':
dwg = {'width': float(el.attrib['width'])}
if 'curve' in el.attrib:
dwg['type'] = 'arc'
dwg['svgpath'] = self._curve_to_svgpath(el)
else:
dwg['type'] = 'segment'
dwg['start'] = [
float(el.attrib['x1']),
-float(el.attrib['y1'])
]
dwg['end'] = [
float(el.attrib['x2']),
-float(el.attrib['y2'])
]
elif el.tag == 'text':
# Text is not currently supported (except refdes)
# due to lack of Eagle font data
pass
elif el.tag == 'circle':
dwg = {
'type': 'circle',
'start': [float(el.attrib['x']), -float(el.attrib['y'])],
'radius': float(el.attrib['radius']),
'width': float(el.attrib['width'])
}
elif el.tag in ['polygonshape', 'polygon']:
dwg = {
'type': 'polygon',
'pos': [0, 0],
'angle': 0,
'polygons': []
}
segs = el if el.tag == 'polygon' \
else el.find('polygonoutlinesegments')
polygon = FusionEagleParser._segments_to_polygon(segs)
dwg['polygons'].append(polygon)
elif el.tag == 'rectangle':
vertices = self._rectangle_vertices(el)
dwg = {
'type': 'polygon',
'pos': [0, 0],
'angle': 0,
'polygons': [[list(v) for v in vertices]]
}
if dwg:
layer_dest[el.attrib['layer']].append(dwg)
def _add_track(self, el, net):
if el.tag == 'via' or (
el.tag == 'wire' and el.attrib['layer'] in
[self.TOP_COPPER_LAYER, self.BOT_COPPER_LAYER]):
trk = {}
if self.config.include_nets:
trk['net'] = net
if el.tag == 'wire':
dest = self.pcbdata['tracks']['F'] \
if el.attrib['layer'] == self.TOP_COPPER_LAYER\
else self.pcbdata['tracks']['B']
if 'curve' in el.attrib:
trk['width'] = float(el.attrib['width'])
# Get SVG parameters for the curve
p = self._curve_to_svgparams(el)
start = complex(p['x1'], p['y1'])
end = complex(p['x2'], p['y2'])
radius = complex(p['r'], p['r'])
large_arc = bool(p['la'])
sweep = bool(p['sw'])
# Pass SVG parameters to get center parameters
arc = Arc(start, radius, 0, large_arc, sweep, end)
# Create arc track from center parameters
trk['center'] = [arc.center.real, arc.center.imag]
trk['radius'] = radius.real
if arc.delta < 0:
trk['startangle'] = arc.theta + arc.delta
trk['endangle'] = arc.theta
else:
trk['startangle'] = arc.theta
trk['endangle'] = arc.theta + arc.delta
dest.append(trk)
else:
trk['start'] = [
float(el.attrib['x1']),
-float(el.attrib['y1'])
]
trk['end'] = [
float(el.attrib['x2']),
-float(el.attrib['y2'])
]
trk['width'] = float(el.attrib['width'])
dest.append(trk)
elif el.tag == 'via':
trk['start'] = [float(el.attrib['x']), -float(el.attrib['y'])]
trk['end'] = trk['start']
trk['width'] = float(el.attrib['drill']) + 2 * self.min_via_w \
if 'diameter' not in el.attrib else float(
el.attrib['diameter'])
self.pcbdata['tracks']['F'].append(trk)
self.pcbdata['tracks']['B'].append(trk)
def _calculate_footprint_bbox(self, package, x, y, angle, mirrored):
_angle = angle if not mirrored else -angle
layers = [
self.TOP_PLACE_LAYER,
self.BOT_PLACE_LAYER,
self.TOP_DOCU_LAYER,
self.BOT_DOCU_LAYER
]
xmax, ymax = -float('inf'), -float('inf')
xmin, ymin = float('inf'), float('inf')
for el in package.iter('wire'):
if el.tag == 'wire' and el.attrib['layer'] in layers:
xmax = max(xmax,
max(float(el.attrib['x1']), float(el.attrib['x2'])))
ymax = max(ymax,
max(float(el.attrib['y1']), float(el.attrib['y2'])))
xmin = min(xmin,
min(float(el.attrib['x1']), float(el.attrib['x2'])))
ymin = min(ymin,
min(float(el.attrib['y1']), float(el.attrib['y2'])))
for el in package.iter():
if el.tag in ['smd', 'pad']:
elx, ely = float(el.attrib['x']), float(el.attrib['y'])
if el.tag == 'smd':
dx, dy = abs(float(el.attrib['dx'])) / 2, abs(
float(el.attrib['dy'])) / 2
else:
d = el.get('diameter')
if d is None:
diameter = float(el.get('drill')) + 2 * self.min_via_w
else:
diameter = float(d)
dx, dy = diameter / 2, diameter / 2
xmax, ymax = max(xmax, elx + dx), max(ymax, ely + dy)
xmin, ymin = min(xmin, elx - dx), min(ymin, ely - dy)
if not math.isinf(xmin):
if mirrored:
xmin, xmax = -xmax, -xmin
dx, dy = self._rotate(xmin, ymax, _angle)
sx = abs(xmax - xmin)
sy = abs(ymax - ymin)
else:
dx, dy = 0, 0
sx, sy = 0, 0
return {
'pos': [x + dx, -y - dy],
'angle': _angle,
'relpos': [0, 0],
'size': [sx, sy]
}
def _footprint_pads(self, package, x, y, angle, mirrored, refdes):
pads = []
element_pad_nets = self.elements_pad_nets.get(refdes)
pin1_allocated = False
for el in package.iter():
if el.tag == 'pad':
elx = float(el.attrib['x'])
ely = -float(el.attrib['y'])
drill = float(el.attrib['drill'])
dx, dy = self._rotate(elx, ely, -angle, mirrored)
diameter = drill + 2 * self.min_via_w \
if 'diameter' not in el.attrib \
else float(el.attrib['diameter'])
pr = self.Rot(el.get('rot'))
if mirrored ^ pr.mirrored:
pad_angle = -angle - pr.angle
else:
pad_angle = angle + pr.angle
pad = {
'layers': ['F', 'B'],
'pos': [x + dx, -y + dy],
'angle': pad_angle,
'type': 'th',
'drillshape': 'circle',
'drillsize': [
drill,
drill
]
}
if el.get('name') in ['1', 'A', 'A1', 'P1', 'PAD1'] and \
not pin1_allocated:
pad['pin1'] = 1
pin1_allocated = True
if 'shape' not in el.attrib or el.attrib['shape'] == 'round':
pad['shape'] = 'circle'
pad['size'] = [diameter, diameter]
elif el.attrib['shape'] == 'square':
pad['shape'] = 'rect'
pad['size'] = [diameter, diameter]
elif el.attrib['shape'] == 'octagon':
pad['shape'] = 'chamfrect'
pad['size'] = [diameter, diameter]
pad['radius'] = 0
pad['chamfpos'] = 0b1111 # all corners
pad['chamfratio'] = 0.333
elif el.attrib['shape'] == 'long':
pad['shape'] = 'roundrect'
pad['radius'] = diameter / 2
pad['size'] = [2 * diameter, diameter]
elif el.attrib['shape'] == 'offset':
pad['shape'] = 'roundrect'
pad['radius'] = diameter / 2
pad['size'] = [2 * diameter, diameter]
pad['offset'] = [diameter / 2, 0]
elif el.attrib['shape'] == 'slot':
pad['shape'] = 'roundrect'
pad['radius'] = diameter / 2
slot_length = float(el.attrib['slotLength'])
pad['size'] = [slot_length + diameter / 2, diameter]
pad['drillshape'] = 'oblong'
pad['drillsize'] = [slot_length, drill]
else:
self.logger.info(
"Unsupported footprint pad shape %s, skipping",
el.attrib['shape'])
if self.config.include_nets and element_pad_nets is not None:
net = element_pad_nets.get(el.attrib['name'])
if net is not None:
pad['net'] = net
pads.append(pad)
elif el.tag == 'smd':
layer = el.attrib['layer']
if layer == '1' and not mirrored or \
layer == '16' and mirrored:
layers = ['F']
elif layer == '1' and mirrored or \
layer == '16' and not mirrored:
layers = ['B']
else:
self.logger.error('Unable to determine layer for '
'{0} pad {1}'.format(refdes,
el.attrib['name']))
layers = None
if layers is not None:
elx = float(el.attrib['x'])
ely = -float(el.attrib['y'])
dx, dy = self._rotate(elx, ely, -angle, mirrored)
pr = self.Rot(el.get('rot'))
if mirrored ^ pr.mirrored:
pad_angle = -angle - pr.angle
else:
pad_angle = angle + pr.angle
pad = {'layers': layers,
'pos': [x + dx, -y + dy],
'size': [
float(el.attrib['dx']),
float(el.attrib['dy'])
],
'angle': pad_angle,
'type': 'smd',
}
if el.get('name') in ['1', 'A', 'A1', 'P1', 'PAD1'] and \
not pin1_allocated:
pad['pin1'] = 1
pin1_allocated = True
if 'roundness' not in el.attrib:
pad['shape'] = 'rect'
else:
pad['shape'] = 'roundrect'
pad['radius'] = (float(el.attrib['roundness']) / 100) \
* float(el.attrib['dy']) / 2
if self.config.include_nets and \
element_pad_nets is not None:
net = element_pad_nets.get(el.attrib['name'])
if net is not None:
pad['net'] = net
pads.append(pad)
return pads
@staticmethod
def _rotate(x, y, angle, mirrored=False):
sin = math.sin(math.radians(angle))
cos = math.cos(math.radians(angle))
xr = x * cos - y * sin
yr = y * cos + x * sin
if mirrored:
return -xr, yr
else:
return xr, yr
def _add_silk_fab(self, el, x, y, angle, mirrored, populate):
if el.tag == 'hole':
dwg_layer = self.pcbdata['edges']
elif el.attrib['layer'] in [self.TOP_PLACE_LAYER, self.BOT_PLACE_LAYER]:
dwg_layer = self.pcbdata['drawings']['silkscreen']
top = el.attrib['layer'] == self.TOP_PLACE_LAYER
elif el.attrib['layer'] in [self.TOP_DOCU_LAYER, self.BOT_DOCU_LAYER]:
if not populate:
return
dwg_layer = self.pcbdata['drawings']['fabrication']
top = el.attrib['layer'] == self.TOP_DOCU_LAYER
else:
return
dwg = None
if el.tag == 'wire':
_dx1 = float(el.attrib['x1'])
_dx2 = float(el.attrib['x2'])
_dy1 = -float(el.attrib['y1'])
_dy2 = -float(el.attrib['y2'])
dx1, dy1 = self._rotate(_dx1, _dy1, -angle, mirrored)
dx2, dy2 = self._rotate(_dx2, _dy2, -angle, mirrored)
x1, y1 = x + dx1, -y + dy1
x2, y2 = x + dx2, -y + dy2
if el.get('curve'):
dwg = {
'type': 'arc',
'width': float(el.attrib['width']),
'svgpath': self._curve_to_svgpath(el, x, y, angle)
}
else:
dwg = {
'type': 'segment',
'start': [x1, y1],
'end': [x2, y2],
'width': float(el.attrib['width'])
}
elif el.tag == 'rectangle':
_dv = self._rectangle_vertices(el)
# Rotate rectangle about component origin based on component angle
dv = [self._rotate(_x, _y, -angle, mirrored) for (_x, _y) in _dv]
# Map vertices back to absolute coordinates
v = [(x + _x, -y + _y) for (_x, _y) in dv]
dwg = {
'type': 'polygon',
'filled': 1,
'pos': [0, 0],
'polygons': [v]
}
elif el.tag in ['circle', 'hole']:
_x = float(el.attrib['x'])
_y = -float(el.attrib['y'])
dxc, dyc = self._rotate(_x, _y, -angle, mirrored)
xc, yc = x + dxc, -y + dyc
if el.tag == 'circle':
radius = float(el.attrib['radius'])
width = float(el.attrib['width'])
else:
radius = float(el.attrib['drill']) / 2
width = 0
dwg = {
'type': 'circle',
'start': [xc, yc],
'radius': radius,
'width': width
}
elif el.tag in ['polygonshape', 'polygon']:
segs = el if el.tag == 'polygon' \
else el.find('polygonoutlinesegments')
dv = self._segments_to_polygon(segs, angle, mirrored)
polygon = [[x + v[0], -y + v[1]] for v in dv]
dwg = {
'type': 'polygon',
'filled': 1,
'pos': [0, 0],
'polygons': [polygon]
}
if dwg is not None:
if el.tag == 'hole':
dwg_layer.append(dwg)
else:
bot = not top
# Note that in Eagle terminology, 'mirrored' essentially means
# 'flipped' (i.e. to the opposite side of the board)
if (mirrored and bot) or (not mirrored and top):
dwg_layer['F'].append(dwg)
elif (mirrored and top) or (not mirrored and bot):
dwg_layer['B'].append(dwg)
def _process_footprint(self, package, x, y, angle, mirrored, populate):
for el in package.iter():
if el.tag in ['wire', 'rectangle', 'circle', 'hole',
'polygonshape', 'polygon', 'hole']:
self._add_silk_fab(el, x, y, angle, mirrored, populate)
def _element_refdes_to_silk(self, el):
for attr in el.iter('attribute'):
if attr.attrib['name'] == 'NAME':
attrx = float(attr.attrib['x'])
attry = -float(attr.attrib['y'])
xpos = attrx
ypos = attry
elr = self.Rot(el.get('rot'))
tr = self.Rot(attr.get('rot'))
text = el.attrib['name']
angle = tr.angle
mirrored = tr.mirrored
spin = elr.spin ^ tr.spin
if mirrored:
angle = -angle
if 'align' not in attr.attrib:
justify = [-1, 1]
elif attr.attrib['align'] == 'center':
justify = [0, 0]
else:
j = attr.attrib['align'].split('-')
alignments = {
'bottom': 1,
'center': 0,
'top': -1,
'left': -1,
'right': 1
}
justify = [alignments[ss] for ss in j[::-1]]
if (90 < angle < 270 and not spin) or \
(-90 >= angle >= -270 and not spin):
angle += 180
justify = [-j for j in justify]
size = float(attr.attrib['size'])
ratio = float(attr.get('ratio', '8')) / 100
dwg = {
'type': 'text',
'text': text,
'pos': [xpos, ypos],
'height': size,
'width': size,
'justify': justify,
'thickness': size * ratio,
'attr': [] if not mirrored else ['mirrored'],
'angle': angle
}
self.font_parser.parse_font_for_string(text)
if mirrored:
self.pcbdata['drawings']['silkscreen']['B'].append(dwg)
else:
self.pcbdata['drawings']['silkscreen']['F'].append(dwg)
@staticmethod
def _segments_to_polygon(segs, angle=0, mirrored=False):
polygon = []
for vertex in segs.iter('vertex'):
_x, _y = float(vertex.attrib['x']), -float(vertex.attrib['y'])
x, y = FusionEagleParser._rotate(_x, _y, -angle, mirrored)
polygon.append([x, y])
return polygon
def _add_zone(self, poly, net):
layer = poly.attrib['layer']
if layer == self.TOP_COPPER_LAYER:
dest = self.pcbdata['zones']['F']
elif layer == self.BOT_COPPER_LAYER:
dest = self.pcbdata['zones']['B']
else:
return
if poly.tag == 'polygonpour':
segs = poly.find('polygonfilldetails').find('polygonshape') \
.find('polygonoutlinesegments')
else:
segs = poly
zone = {'polygons': []}
zone['polygons'].append(self._segments_to_polygon(segs))
if self.config.include_nets:
zone['net'] = net
dest.append(zone)
def _add_parsed_font_data(self):
for (c, wl) in self.font_parser.get_parsed_font().items():
if c not in self.pcbdata['font_data']:
self.pcbdata['font_data'][c] = wl
def parse(self):
ext = os.path.splitext(self.file_name)[1]
if ext.lower() == '.fbrd':
with zipfile.ZipFile(self.file_name) as myzip:
brdfilename = [fname for fname in myzip.namelist() if
os.path.splitext(fname)[1] == '.brd']
with myzip.open(brdfilename[0]) as brdfile:
return self._parse(brdfile)
elif ext.lower() == '.brd':
with io.open(self.file_name, 'r', encoding='utf-8') as brdfile:
return self._parse(brdfile)
def _parse(self, brdfile):
try:
brdxml = ElementTree.parse(brdfile)
except ElementTree.ParseError as err:
self.logger.error("Exception occurred trying to parse {0}, message:"
" {1}"
.format(brdfile.name, err.msg))
return None, None
if brdxml is None:
self.logger.error("No data was able to be parsed from {0}"
.format(brdfile.name))
return None, None
# Pick out key sections
root = brdxml.getroot()
board = root.find('drawing').find('board')
plain = board.find('plain')
elements = board.find('elements')
signals = board.find('signals')
# Build library mapping elements' pads to nets
self._parse_pad_nets(signals)
# Determine minimum via annular ring from board design rules
# (Needed in order to calculate through-hole pad diameters correctly)
mv = [el.attrib['value'] for el in root.iter('param') if
el.attrib['name'] == 'rlMinViaOuter']
if len(mv) == 0:
self.logger.warning("rlMinViaOuter not found, defaulting to 0")
self.min_via_w = 0
else:
if len(mv) > 1:
self.logger.warning("Multiple rlMinViaOuter found, using first "
"occurrence")
mv = mv[0]
mv_val = float(''.join(d for d in mv if d in string.digits + '.'))
mv_units = (''.join(d for d in mv if d in string.ascii_lowercase))
if mv_units == 'mm':
self.min_via_w = mv_val
elif mv_units == 'mil':
self.min_via_w = mv_val * 0.0254
else:
self.logger.error("Unsupported units %s on rlMinViaOuter",
mv_units)
# Edges & silkscreen (partial)
for el in plain.iter():
self._add_drawing(el)
# identify board bounding box based on edges
board_outline_bbox = BoundingBox()
for drawing in self.pcbdata['edges']:
self.add_drawing_bounding_box(drawing, board_outline_bbox)
if board_outline_bbox.initialized():
self.pcbdata['edges_bbox'] = board_outline_bbox.to_dict()
# Signals --> nets
if self.config.include_nets:
self.pcbdata['nets'] = []
for signal in signals.iter('signal'):
self.pcbdata['nets'].append(signal.attrib['name'])
# Signals --> tracks, zones
if self.config.include_tracks:
self.pcbdata['tracks'] = {'F': [], 'B': []}
self.pcbdata['zones'] = {'F': [], 'B': []}
for signal in signals.iter('signal'):
for wire in signal.iter('wire'):
self._add_track(wire, signal.attrib['name'])
for via in signal.iter('via'):
self._add_track(via, signal.attrib['name'])
for poly in signal.iter('polygonpour'):
self._add_zone(poly, signal.attrib['name'])
for poly in signal.iter('polygon'):
self._add_zone(poly, signal.attrib['name'])
# Elements --> components, footprints, silkscreen
for el in elements.iter('element'):
populate = el.get('populate') != 'no'
elr = self.Rot(el.get('rot'))
layer = 'B' if elr.mirrored else 'F'
extra_fields = {}
for a in el.iter('attribute'):
if 'value' in a.attrib:
extra_fields[a.attrib['name']] = a.attrib['value']
comp = Component(ref=el.attrib['name'],
val='' if 'value' not in el.attrib else el.attrib[
'value'],
footprint=el.attrib['package'],
layer=layer,
attr=None,
extra_fields=extra_fields)
# For component, get footprint data
libs = [lib for lib in board.find('libraries').findall('library')
if lib.attrib['name'] == el.attrib['library']]
packages = []
for lib in libs:
p = [pac for pac in lib.find('packages').findall('package')
if pac.attrib['name'] == el.attrib['package']]
packages.extend(p)
if not packages:
self.logger.error("Package {0} in library {1} not found in "
"source file {2} for element {3}"
.format(el.attrib['package'],
el.attrib['library'],
brdfile.name,
el.attrib['name']))
return None, None
else:
package = packages[0]
if len(packages) > 1:
self.logger.warn("Multiple packages found for package {0}"
" in library {1}, using first instance "
"found".format(el.attrib['package'],
el.attrib['library']))
elx = float(el.attrib['x'])
ely = float(el.attrib['y'])
refdes = el.attrib['name']
footprint = {
'ref': refdes,
'center': [elx, ely],
'pads': [],
'drawings': [],
'layer': layer
}
elr = self.Rot(el.get('rot'))
footprint['pads'] = self._footprint_pads(package, elx, ely,
elr.angle, elr.mirrored,
refdes)
footprint['bbox'] = self._calculate_footprint_bbox(package, elx,
ely, elr.angle,
elr.mirrored)
self.pcbdata['footprints'].append(footprint)
# Add silkscreen for component footprint & refdes
self._process_footprint(package, elx, ely, elr.angle, elr.mirrored,
populate)
self._element_refdes_to_silk(el)
if populate:
self.components.append(comp)
self._add_parsed_font_data()
# Fabrication & metadata
company = [a.attrib['value'] for a in root.iter('attribute') if
a.attrib['name'] == 'COMPANY']
company = '' if not company else company[0]
rev = [a.attrib['value'] for a in root.iter('attribute') if
a.attrib['name'] == 'REVISION']
rev = '' if not rev else rev[0]
if not rev:
rev = ''
title = os.path.basename(self.file_name)
variant = [a.attrib['name'] for a in root.iter('variantdef') if
a.get('current') == 'yes']
variant = None if not variant else variant[0]
if variant:
title = "{0}, Variant: {1}".format(title, variant)
date = datetime.fromtimestamp(
os.path.getmtime(self.file_name)).strftime('%Y-%m-%d %H:%M:%S')
self.pcbdata['metadata'] = {'title': title, 'revision': rev,
'company': company, 'date': date}
return self.pcbdata, self.components

View File

@ -0,0 +1,163 @@
import io
import json
import os.path
from jsonschema import validate, ValidationError
from .common import EcadParser, Component, BoundingBox
from ..core.fontparser import FontParser
from ..errors import ParsingException
class GenericJsonParser(EcadParser):
COMPATIBLE_SPEC_VERSIONS = [1]
def extra_data_file_filter(self):
return "Json file ({f})|{f}".format(f=os.path.basename(self.file_name))
def latest_extra_data(self, extra_dirs=None):
return self.file_name
def get_extra_field_data(self, file_name):
if os.path.abspath(file_name) != os.path.abspath(self.file_name):
return None
_, components = self._parse()
field_set = set()
comp_dict = {}
for c in components:
ref_fields = comp_dict.setdefault(c.ref, {})
for k, v in c.extra_fields.items():
field_set.add(k)
ref_fields[k] = v
return list(field_set), comp_dict
def get_generic_json_pcb(self):
with io.open(self.file_name, 'r', encoding='utf-8') as f:
pcb = json.load(f)
if 'spec_version' not in pcb:
raise ValidationError("'spec_version' is a required property")
if pcb['spec_version'] not in self.COMPATIBLE_SPEC_VERSIONS:
raise ValidationError("Unsupported spec_version ({})"
.format(pcb['spec_version']))
schema_dir = os.path.join(os.path.dirname(__file__), 'schema')
schema_file_name = os.path.join(schema_dir,
'genericjsonpcbdata_v{}.schema'.format(
pcb['spec_version']))
with io.open(schema_file_name, 'r', encoding='utf-8') as f:
schema = json.load(f)
validate(instance=pcb, schema=schema)
return pcb
def _verify(self, pcb):
"""Spot check the pcb object."""
if len(pcb['pcbdata']['footprints']) != len(pcb['components']):
self.logger.error("Length of components list doesn't match"
" length of footprints list.")
return False
return True
@staticmethod
def _texts(pcbdata):
for layer in pcbdata['drawings'].values():
for side in layer.values():
for dwg in side:
if 'text' in dwg:
yield dwg
@staticmethod
def _remove_control_codes(s):
import unicodedata
return ''.join(c for c in s if unicodedata.category(c)[0] != "C")
def _parse_font_data(self, pcbdata):
font_parser = FontParser()
for dwg in self._texts(pcbdata):
if 'svgpath' not in dwg:
dwg['text'] = self._remove_control_codes(dwg['text'])
font_parser.parse_font_for_string(dwg['text'])
if font_parser.get_parsed_font():
pcbdata['font_data'] = font_parser.get_parsed_font()
def _check_font_data(self, pcbdata):
mc = set()
for dwg in self._texts(pcbdata):
dwg['text'] = self._remove_control_codes(dwg['text'])
mc.update({c for c in dwg['text'] if 'svgpath' not in dwg and
c not in pcbdata['font_data']})
if mc:
s = ''.join(mc)
self.logger.error('Provided font_data is missing character(s)'
f' "{s}" that are present in text drawing'
' objects')
return False
else:
return True
def _parse(self):
try:
pcb = self.get_generic_json_pcb()
except ValidationError as e:
self.logger.error('File {f} does not comply with json schema. {m}'
.format(f=self.file_name, m=e.message))
return None, None
if not self._verify(pcb):
self.logger.error('File {} does not appear to be valid generic'
' InteractiveHtmlBom json file.'
.format(self.file_name))
return None, None
pcbdata = pcb['pcbdata']
components = [Component(**c) for c in pcb['components']]
if 'font_data' in pcbdata:
if not self._check_font_data(pcbdata):
raise ParsingException(f'Failed parsing {self.file_name}')
else:
self._parse_font_data(pcbdata)
if 'font_data' in pcbdata:
self.logger.info('No font_data provided in JSON, using '
'newstroke font')
self.logger.info('Successfully parsed {}'.format(self.file_name))
return pcbdata, components
def parse(self):
pcbdata, components = self._parse()
# override board bounding box based on edges
board_outline_bbox = BoundingBox()
for drawing in pcbdata['edges']:
self.add_drawing_bounding_box(drawing, board_outline_bbox)
if board_outline_bbox.initialized():
pcbdata['edges_bbox'] = board_outline_bbox.to_dict()
extra_fields = set(self.config.show_fields)
extra_fields.discard("Footprint")
extra_fields.discard("Value")
if self.config.dnp_field:
extra_fields.add(self.config.dnp_field)
if self.config.board_variant_field:
extra_fields.add(self.config.board_variant_field)
if extra_fields:
for c in components:
c.extra_fields = {
f: c.extra_fields.get(f, "") for f in extra_fields}
self.config.kicad_text_formatting = False
return pcbdata, components

View File

@ -0,0 +1,813 @@
import os
from datetime import datetime
import pcbnew
from .common import EcadParser, Component
from .kicad_extra import find_latest_schematic_data, parse_schematic_data
from .svgpath import create_path
from ..core import ibom
from ..core.config import Config
from ..core.fontparser import FontParser
class PcbnewParser(EcadParser):
def __init__(self, file_name, config, logger, board=None):
super(PcbnewParser, self).__init__(file_name, config, logger)
self.board = board
if self.board is None:
self.board = pcbnew.LoadBoard(self.file_name) # type: pcbnew.BOARD
if hasattr(self.board, 'GetModules'):
self.footprints = list(self.board.GetModules())
else:
self.footprints = list(self.board.GetFootprints())
self.font_parser = FontParser()
def get_extra_field_data(self, file_name):
if os.path.abspath(file_name) == os.path.abspath(self.file_name):
return self.parse_extra_data_from_pcb()
if os.path.splitext(file_name)[1] == '.kicad_pcb':
return None
return parse_schematic_data(file_name)
def parse_extra_data_from_pcb(self):
field_set = set()
comp_dict = {}
for f in self.footprints: # type: pcbnew.FOOTPRINT
props = f.GetProperties()
ref = f.GetReference()
ref_fields = comp_dict.setdefault(ref, {})
for k, v in props.items():
field_set.add(k)
ref_fields[k] = v
return list(field_set), comp_dict
def latest_extra_data(self, extra_dirs=None):
base_name = os.path.splitext(os.path.basename(self.file_name))[0]
extra_dirs.append(self.board.GetPlotOptions().GetOutputDirectory())
file_dir_name = os.path.dirname(self.file_name)
directories = [file_dir_name]
for dir in extra_dirs:
if not os.path.isabs(dir):
dir = os.path.join(file_dir_name, dir)
if os.path.exists(dir):
directories.append(dir)
return find_latest_schematic_data(base_name, directories)
def extra_data_file_filter(self):
if hasattr(self.board, 'GetModules'):
return "Netlist and xml files (*.net; *.xml)|*.net;*.xml"
else:
return ("Netlist, xml and pcb files (*.net; *.xml; *.kicad_pcb)|"
"*.net;*.xml;*.kicad_pcb")
@staticmethod
def normalize(point):
return [point.x * 1e-6, point.y * 1e-6]
@staticmethod
def normalize_angle(angle):
if isinstance(angle, int) or isinstance(angle, float):
return angle * 0.1
else:
return angle.AsDegrees()
def get_arc_angles(self, d):
# type: (pcbnew.PCB_SHAPE) -> tuple
a1 = self.normalize_angle(d.GetArcAngleStart())
if hasattr(d, "GetAngle"):
a2 = a1 + self.normalize_angle(d.GetAngle())
else:
a2 = a1 + self.normalize_angle(d.GetArcAngle())
if a2 < a1:
a1, a2 = a2, a1
return round(a1, 2), round(a2, 2)
def parse_shape(self, d):
# type: (pcbnew.PCB_SHAPE) -> dict or None
shape = {
pcbnew.S_SEGMENT: "segment",
pcbnew.S_CIRCLE: "circle",
pcbnew.S_ARC: "arc",
pcbnew.S_POLYGON: "polygon",
pcbnew.S_CURVE: "curve",
pcbnew.S_RECT: "rect",
}.get(d.GetShape(), "")
if shape == "":
self.logger.info("Unsupported shape %s, skipping", d.GetShape())
return None
start = self.normalize(d.GetStart())
end = self.normalize(d.GetEnd())
if shape == "segment":
return {
"type": shape,
"start": start,
"end": end,
"width": d.GetWidth() * 1e-6
}
if shape == "rect":
if hasattr(d, "GetRectCorners"):
points = list(map(self.normalize, d.GetRectCorners()))
else:
points = [
start,
[end[0], start[1]],
end,
[start[0], end[1]]
]
shape_dict = {
"type": "polygon",
"pos": [0, 0],
"angle": 0,
"polygons": [points],
"width": d.GetWidth() * 1e-6,
"filled": 0
}
if hasattr(d, "IsFilled") and d.IsFilled():
shape_dict["filled"] = 1
return shape_dict
if shape == "circle":
shape_dict = {
"type": shape,
"start": start,
"radius": d.GetRadius() * 1e-6,
"width": d.GetWidth() * 1e-6
}
if hasattr(d, "IsFilled") and d.IsFilled():
shape_dict["filled"] = 1
return shape_dict
if shape == "arc":
a1, a2 = self.get_arc_angles(d)
if hasattr(d, "GetCenter"):
start = self.normalize(d.GetCenter())
return {
"type": shape,
"start": start,
"radius": d.GetRadius() * 1e-6,
"startangle": a1,
"endangle": a2,
"width": d.GetWidth() * 1e-6
}
if shape == "polygon":
if hasattr(d, "GetPolyShape"):
polygons = self.parse_poly_set(d.GetPolyShape())
else:
self.logger.info(
"Polygons not supported for KiCad 4, skipping")
return None
angle = 0
if hasattr(d, 'GetParentModule'):
parent_footprint = d.GetParentModule()
else:
parent_footprint = d.GetParentFootprint()
if parent_footprint is not None:
angle = self.normalize_angle(parent_footprint.GetOrientation())
shape_dict = {
"type": shape,
"pos": start,
"angle": angle,
"polygons": polygons
}
if hasattr(d, "IsFilled") and not d.IsFilled():
shape_dict["filled"] = 0
shape_dict["width"] = d.GetWidth() * 1e-6
return shape_dict
if shape == "curve":
if hasattr(d, "GetBezierC1"):
c1 = self.normalize(d.GetBezierC1())
c2 = self.normalize(d.GetBezierC2())
else:
c1 = self.normalize(d.GetBezControl1())
c2 = self.normalize(d.GetBezControl2())
return {
"type": shape,
"start": start,
"cpa": c1,
"cpb": c2,
"end": end,
"width": d.GetWidth() * 1e-6
}
def parse_line_chain(self, shape):
# type: (pcbnew.SHAPE_LINE_CHAIN) -> list
result = []
if not hasattr(shape, "PointCount"):
self.logger.warn("No PointCount method on outline object. "
"Unpatched kicad version?")
return result
for point_index in range(shape.PointCount()):
result.append(
self.normalize(shape.CPoint(point_index)))
return result
def parse_poly_set(self, poly):
# type: (pcbnew.SHAPE_POLY_SET) -> list
result = []
for i in range(poly.OutlineCount()):
result.append(self.parse_line_chain(poly.Outline(i)))
return result
def parse_text(self, d):
# type: (pcbnew.PCB_TEXT) -> dict
if not d.IsVisible() and d.GetClass() not in ["PTEXT", "PCB_TEXT"]:
return None
pos = self.normalize(d.GetPosition())
if hasattr(d, "GetTextThickness"):
thickness = d.GetTextThickness() * 1e-6
else:
thickness = d.GetThickness() * 1e-6
if hasattr(d, 'TransformToSegmentList'):
segments = [self.normalize(p) for p in d.TransformToSegmentList()]
lines = []
for i in range(0, len(segments), 2):
if i == 0 or segments[i - 1] != segments[i]:
lines.append([segments[i]])
lines[-1].append(segments[i + 1])
return {
"thickness": thickness,
"svgpath": create_path(lines)
}
elif hasattr(d, 'GetEffectiveTextShape'):
shape = d.GetEffectiveTextShape(
aTriangulate=False) # type: pcbnew.SHAPE_COMPOUND
segments = []
polygons = []
for s in shape.GetSubshapes():
if s.Type() == pcbnew.SH_LINE_CHAIN:
polygons.append(self.parse_line_chain(s))
elif s.Type() == pcbnew.SH_SEGMENT:
seg = s.GetSeg()
segments.append(
[self.normalize(seg.A), self.normalize(seg.B)])
else:
self.logger.warn(
"Unsupported subshape in text: %s" % s.Type())
if segments:
return {
"thickness": thickness,
"svgpath": create_path(segments)
}
else:
return {
"polygons": polygons
}
if d.GetClass() == "MTEXT":
angle = self.normalize_angle(d.GetDrawRotation())
else:
if hasattr(d, "GetTextAngle"):
angle = self.normalize_angle(d.GetTextAngle())
else:
angle = self.normalize_angle(d.GetOrientation())
if hasattr(d, "GetTextHeight"):
height = d.GetTextHeight() * 1e-6
width = d.GetTextWidth() * 1e-6
else:
height = d.GetHeight() * 1e-6
width = d.GetWidth() * 1e-6
if hasattr(d, "GetShownText"):
text = d.GetShownText()
else:
text = d.GetText()
self.font_parser.parse_font_for_string(text)
attributes = []
if d.IsMirrored():
attributes.append("mirrored")
if d.IsItalic():
attributes.append("italic")
if d.IsBold():
attributes.append("bold")
return {
"pos": pos,
"text": text,
"height": height,
"width": width,
"justify": [d.GetHorizJustify(), d.GetVertJustify()],
"thickness": thickness,
"attr": attributes,
"angle": angle
}
def parse_dimension(self, d):
# type: (pcbnew.PCB_DIMENSION_BASE) -> dict
segments = []
circles = []
for s in d.GetShapes():
s = s.Cast()
if s.Type() == pcbnew.SH_SEGMENT:
seg = s.GetSeg()
segments.append(
[self.normalize(seg.A), self.normalize(seg.B)])
elif s.Type() == pcbnew.SH_CIRCLE:
circles.append(
[self.normalize(s.GetCenter()), s.GetRadius() * 1e-6])
else:
self.logger.info(
"Unsupported shape type in dimension object: %s", s.Type())
svgpath = create_path(segments, circles=circles)
return {
"thickness": d.GetLineThickness() * 1e-6,
"svgpath": svgpath
}
def parse_drawing(self, d):
# type: (pcbnew.BOARD_ITEM) -> list
result = []
s = None
if d.GetClass() in ["DRAWSEGMENT", "MGRAPHIC", "PCB_SHAPE"]:
s = self.parse_shape(d)
elif d.GetClass() in ["PTEXT", "MTEXT", "FP_TEXT", "PCB_TEXT"]:
s = self.parse_text(d)
elif (d.GetClass().startswith("PCB_DIM")
and hasattr(pcbnew, "VECTOR_SHAPEPTR")):
result.append(self.parse_dimension(d))
if hasattr(d, "Text"):
s = self.parse_text(d.Text())
else:
s = self.parse_text(d)
else:
self.logger.info("Unsupported drawing class %s, skipping",
d.GetClass())
if s:
result.append(s)
return result
def parse_edges(self, pcb):
edges = []
drawings = list(pcb.GetDrawings())
bbox = None
for f in self.footprints:
for g in f.GraphicalItems():
drawings.append(g)
for d in drawings:
if d.GetLayer() == pcbnew.Edge_Cuts:
for parsed_drawing in self.parse_drawing(d):
edges.append(parsed_drawing)
if bbox is None:
bbox = d.GetBoundingBox()
else:
bbox.Merge(d.GetBoundingBox())
if bbox:
bbox.Normalize()
return edges, bbox
def parse_drawings_on_layers(self, drawings, f_layer, b_layer):
front = []
back = []
for d in drawings:
if d[1].GetLayer() not in [f_layer, b_layer]:
continue
for drawing in self.parse_drawing(d[1]):
if d[0] in ["ref", "val"]:
drawing[d[0]] = 1
if d[1].GetLayer() == f_layer:
front.append(drawing)
else:
back.append(drawing)
return {
"F": front,
"B": back
}
def get_all_drawings(self):
drawings = [(d.GetClass(), d) for d in list(self.board.GetDrawings())]
for f in self.footprints:
drawings.append(("ref", f.Reference()))
drawings.append(("val", f.Value()))
for d in f.GraphicalItems():
drawings.append((d.GetClass(), d))
return drawings
def parse_pad(self, pad):
# type: (pcbnew.PAD) -> dict or None
layers_set = list(pad.GetLayerSet().Seq())
layers = []
if pcbnew.F_Cu in layers_set:
layers.append("F")
if pcbnew.B_Cu in layers_set:
layers.append("B")
pos = self.normalize(pad.GetPosition())
size = self.normalize(pad.GetSize())
angle = self.normalize_angle(pad.GetOrientation())
shape_lookup = {
pcbnew.PAD_SHAPE_RECT: "rect",
pcbnew.PAD_SHAPE_OVAL: "oval",
pcbnew.PAD_SHAPE_CIRCLE: "circle",
}
if hasattr(pcbnew, "PAD_SHAPE_TRAPEZOID"):
shape_lookup[pcbnew.PAD_SHAPE_TRAPEZOID] = "trapezoid"
if hasattr(pcbnew, "PAD_SHAPE_ROUNDRECT"):
shape_lookup[pcbnew.PAD_SHAPE_ROUNDRECT] = "roundrect"
if hasattr(pcbnew, "PAD_SHAPE_CUSTOM"):
shape_lookup[pcbnew.PAD_SHAPE_CUSTOM] = "custom"
if hasattr(pcbnew, "PAD_SHAPE_CHAMFERED_RECT"):
shape_lookup[pcbnew.PAD_SHAPE_CHAMFERED_RECT] = "chamfrect"
shape = shape_lookup.get(pad.GetShape(), "")
if shape == "":
self.logger.info("Unsupported pad shape %s, skipping.",
pad.GetShape())
return None
pad_dict = {
"layers": layers,
"pos": pos,
"size": size,
"angle": angle,
"shape": shape
}
if shape == "custom":
polygon_set = pad.GetCustomShapeAsPolygon()
if polygon_set.HasHoles():
self.logger.warn('Detected holes in custom pad polygons')
pad_dict["polygons"] = self.parse_poly_set(polygon_set)
if shape == "trapezoid":
# treat trapezoid as custom shape
pad_dict["shape"] = "custom"
delta = self.normalize(pad.GetDelta())
pad_dict["polygons"] = [[
[size[0] / 2 + delta[1] / 2, size[1] / 2 - delta[0] / 2],
[-size[0] / 2 - delta[1] / 2, size[1] / 2 + delta[0] / 2],
[-size[0] / 2 + delta[1] / 2, -size[1] / 2 - delta[0] / 2],
[size[0] / 2 - delta[1] / 2, -size[1] / 2 + delta[0] / 2],
]]
if shape in ["roundrect", "chamfrect"]:
pad_dict["radius"] = pad.GetRoundRectCornerRadius() * 1e-6
if shape == "chamfrect":
pad_dict["chamfpos"] = pad.GetChamferPositions()
pad_dict["chamfratio"] = pad.GetChamferRectRatio()
if hasattr(pcbnew, 'PAD_ATTRIB_PTH'):
through_hole_attributes = [pcbnew.PAD_ATTRIB_PTH,
pcbnew.PAD_ATTRIB_NPTH]
else:
through_hole_attributes = [pcbnew.PAD_ATTRIB_STANDARD,
pcbnew.PAD_ATTRIB_HOLE_NOT_PLATED]
if pad.GetAttribute() in through_hole_attributes:
pad_dict["type"] = "th"
pad_dict["drillshape"] = {
pcbnew.PAD_DRILL_SHAPE_CIRCLE: "circle",
pcbnew.PAD_DRILL_SHAPE_OBLONG: "oblong"
}.get(pad.GetDrillShape())
pad_dict["drillsize"] = self.normalize(pad.GetDrillSize())
else:
pad_dict["type"] = "smd"
if hasattr(pad, "GetOffset"):
pad_dict["offset"] = self.normalize(pad.GetOffset())
if self.config.include_nets:
pad_dict["net"] = pad.GetNetname()
return pad_dict
def parse_footprints(self):
# type: () -> list
footprints = []
for f in self.footprints: # type: pcbnew.FOOTPRINT
ref = f.GetReference()
# bounding box
if hasattr(pcbnew, 'MODULE'):
f_copy = pcbnew.MODULE(f)
else:
f_copy = pcbnew.FOOTPRINT(f)
try:
f_copy.SetOrientation(0)
except TypeError:
f_copy.SetOrientation(
pcbnew.EDA_ANGLE(0, pcbnew.TENTHS_OF_A_DEGREE_T))
pos = f_copy.GetPosition()
pos.x = pos.y = 0
f_copy.SetPosition(pos)
if hasattr(f_copy, 'GetFootprintRect'):
footprint_rect = f_copy.GetFootprintRect()
else:
footprint_rect = f_copy.GetBoundingBox(False, False)
bbox = {
"pos": self.normalize(f.GetPosition()),
"relpos": self.normalize(footprint_rect.GetPosition()),
"size": self.normalize(footprint_rect.GetSize()),
"angle": self.normalize_angle(f.GetOrientation()),
}
# graphical drawings
drawings = []
for d in f.GraphicalItems():
# we only care about copper ones, silkscreen is taken care of
if d.GetLayer() not in [pcbnew.F_Cu, pcbnew.B_Cu]:
continue
for drawing in self.parse_drawing(d):
drawings.append({
"layer": "F" if d.GetLayer() == pcbnew.F_Cu else "B",
"drawing": drawing,
})
# footprint pads
pads = []
for p in f.Pads():
pad_dict = self.parse_pad(p)
if pad_dict is not None:
pads.append((p.GetPadName(), pad_dict))
if pads:
# Try to guess first pin name.
pads = sorted(pads, key=lambda el: el[0])
pin1_pads = [p for p in pads if p[0] in
['1', 'A', 'A1', 'P1', 'PAD1']]
if pin1_pads:
pin1_pad_name = pin1_pads[0][0]
else:
# No pads have common first pin name,
# pick lexicographically smallest.
pin1_pad_name = pads[0][0]
for pad_name, pad_dict in pads:
if pad_name == pin1_pad_name:
pad_dict['pin1'] = 1
pads = [p[1] for p in pads]
# add footprint
footprints.append({
"ref": ref,
"bbox": bbox,
"pads": pads,
"drawings": drawings,
"layer": {
pcbnew.F_Cu: "F",
pcbnew.B_Cu: "B"
}.get(f.GetLayer())
})
return footprints
def parse_tracks(self, tracks):
result = {pcbnew.F_Cu: [], pcbnew.B_Cu: []}
for track in tracks:
if track.GetClass() in ["VIA", "PCB_VIA"]:
track_dict = {
"start": self.normalize(track.GetStart()),
"end": self.normalize(track.GetEnd()),
"width": track.GetWidth() * 1e-6,
"net": track.GetNetname(),
}
for layer in [pcbnew.F_Cu, pcbnew.B_Cu]:
if track.IsOnLayer(layer):
result[layer].append(track_dict)
else:
if track.GetLayer() in [pcbnew.F_Cu, pcbnew.B_Cu]:
if track.GetClass() in ["ARC", "PCB_ARC"]:
a1, a2 = self.get_arc_angles(track)
track_dict = {
"center": self.normalize(track.GetCenter()),
"startangle": a1,
"endangle": a2,
"radius": track.GetRadius() * 1e-6,
"width": track.GetWidth() * 1e-6,
}
else:
track_dict = {
"start": self.normalize(track.GetStart()),
"end": self.normalize(track.GetEnd()),
"width": track.GetWidth() * 1e-6,
}
if self.config.include_nets:
track_dict["net"] = track.GetNetname()
result[track.GetLayer()].append(track_dict)
return {
'F': result.get(pcbnew.F_Cu),
'B': result.get(pcbnew.B_Cu)
}
def parse_zones(self, zones):
result = {pcbnew.F_Cu: [], pcbnew.B_Cu: []}
for zone in zones: # type: pcbnew.ZONE
if (not zone.IsFilled() or
hasattr(zone, 'GetIsKeepout') and zone.GetIsKeepout() or
hasattr(zone, 'GetIsRuleArea') and zone.GetIsRuleArea()):
continue
layers = [layer for layer in list(zone.GetLayerSet().Seq())
if layer in [pcbnew.F_Cu, pcbnew.B_Cu]]
for layer in layers:
try:
# kicad 5.1 and earlier
poly_set = zone.GetFilledPolysList()
except TypeError:
poly_set = zone.GetFilledPolysList(layer)
width = zone.GetMinThickness() * 1e-6
if (hasattr(zone, 'GetFilledPolysUseThickness') and
not zone.GetFilledPolysUseThickness()):
width = 0
zone_dict = {
"polygons": self.parse_poly_set(poly_set),
"width": width,
}
if self.config.include_nets:
zone_dict["net"] = zone.GetNetname()
result[layer].append(zone_dict)
return {
'F': result.get(pcbnew.F_Cu),
'B': result.get(pcbnew.B_Cu)
}
@staticmethod
def parse_netlist(net_info):
# type: (pcbnew.NETINFO_LIST) -> list
nets = net_info.NetsByName().asdict().keys()
nets = sorted([str(s) for s in nets])
return nets
@staticmethod
def footprint_to_component(footprint, extra_fields):
try:
footprint_name = str(footprint.GetFPID().GetFootprintName())
except AttributeError:
footprint_name = str(footprint.GetFPID().GetLibItemName())
attr = 'Normal'
if hasattr(pcbnew, 'FP_EXCLUDE_FROM_BOM'):
if footprint.GetAttributes() & pcbnew.FP_EXCLUDE_FROM_BOM:
attr = 'Virtual'
elif hasattr(pcbnew, 'MOD_VIRTUAL'):
if footprint.GetAttributes() == pcbnew.MOD_VIRTUAL:
attr = 'Virtual'
layer = {
pcbnew.F_Cu: 'F',
pcbnew.B_Cu: 'B',
}.get(footprint.GetLayer())
return Component(footprint.GetReference(),
footprint.GetValue(),
footprint_name,
layer,
attr,
extra_fields)
def parse(self):
from ..errors import ParsingException
# Get extra field data from netlist
field_set = set(self.config.show_fields)
field_set.discard("Value")
field_set.discard("Footprint")
need_extra_fields = (field_set or
self.config.board_variant_whitelist or
self.config.board_variant_blacklist or
self.config.dnp_field)
if not self.config.extra_data_file and need_extra_fields:
self.logger.warn('Ignoring extra fields related config parameters '
'since no netlist/xml file was specified.')
need_extra_fields = False
extra_field_data = None
if (self.config.extra_data_file and
os.path.isfile(self.config.extra_data_file)):
extra_field_data = self.parse_extra_data(
self.config.extra_data_file, self.config.normalize_field_case)
if extra_field_data is None and need_extra_fields:
raise ParsingException(
'Failed parsing %s' % self.config.extra_data_file)
extra_field_data = extra_field_data[1] if extra_field_data else None
title_block = self.board.GetTitleBlock()
title = title_block.GetTitle()
revision = title_block.GetRevision()
company = title_block.GetCompany()
file_date = title_block.GetDate()
if (hasattr(self.board, "GetProject") and
hasattr(pcbnew, "ExpandTextVars")):
project = self.board.GetProject()
title = pcbnew.ExpandTextVars(title, project)
revision = pcbnew.ExpandTextVars(revision, project)
company = pcbnew.ExpandTextVars(company, project)
file_date = pcbnew.ExpandTextVars(file_date, project)
if not file_date:
file_mtime = os.path.getmtime(self.file_name)
file_date = datetime.fromtimestamp(file_mtime).strftime(
'%Y-%m-%d %H:%M:%S')
pcb_file_name = os.path.basename(self.file_name)
if not title:
# remove .kicad_pcb extension
title = os.path.splitext(pcb_file_name)[0]
edges, bbox = self.parse_edges(self.board)
if bbox is None:
self.logger.error('Please draw pcb outline on the edges '
'layer on sheet or any footprint before '
'generating BOM.')
return None, None
bbox = {
"minx": bbox.GetPosition().x * 1e-6,
"miny": bbox.GetPosition().y * 1e-6,
"maxx": bbox.GetRight() * 1e-6,
"maxy": bbox.GetBottom() * 1e-6,
}
drawings = self.get_all_drawings()
pcbdata = {
"edges_bbox": bbox,
"edges": edges,
"drawings": {
"silkscreen": self.parse_drawings_on_layers(
drawings, pcbnew.F_SilkS, pcbnew.B_SilkS),
"fabrication": self.parse_drawings_on_layers(
drawings, pcbnew.F_Fab, pcbnew.B_Fab),
},
"footprints": self.parse_footprints(),
"metadata": {
"title": title,
"revision": revision,
"company": company,
"date": file_date,
},
"bom": {},
"font_data": self.font_parser.get_parsed_font()
}
if self.config.include_tracks:
pcbdata["tracks"] = self.parse_tracks(self.board.GetTracks())
if hasattr(self.board, "Zones"):
pcbdata["zones"] = self.parse_zones(self.board.Zones())
else:
self.logger.info("Zones not supported for KiCad 4, skipping")
pcbdata["zones"] = {'F': [], 'B': []}
if self.config.include_nets and hasattr(self.board, "GetNetInfo"):
pcbdata["nets"] = self.parse_netlist(self.board.GetNetInfo())
warning_shown = False
if extra_field_data and need_extra_fields:
e = []
for f in self.footprints:
e.append(extra_field_data.get(f.GetReference(), {}))
if f.GetReference() not in extra_field_data:
# Some components are on pcb but not in schematic data.
# Show a warning about possibly outdated netlist/xml file.
self.logger.warn(
'Component %s is missing from schematic data.'
% f.GetReference())
warning_shown = True
else:
e = [{}] * len(self.footprints)
if warning_shown:
self.logger.warn('Netlist/xml file is likely out of date.')
components = [self.footprint_to_component(f, ee)
for (f, ee) in zip(self.footprints, e)]
return pcbdata, components
class InteractiveHtmlBomPlugin(pcbnew.ActionPlugin, object):
def __init__(self):
super(InteractiveHtmlBomPlugin, self).__init__()
self.name = "Generate Interactive HTML BOM"
self.category = "Read PCB"
self.pcbnew_icon_support = hasattr(self, "show_toolbar_button")
self.show_toolbar_button = True
icon_dir = os.path.dirname(os.path.dirname(__file__))
self.icon_file_name = os.path.join(icon_dir, 'icon.png')
self.description = "Generate interactive HTML page with BOM " \
"table and pcb drawing."
def defaults(self):
pass
def Run(self):
from ..version import version
from ..errors import ParsingException
logger = ibom.Logger()
board = pcbnew.GetBoard()
pcb_file_name = board.GetFileName()
if not pcb_file_name:
logger.error('Please save the board file before generating BOM.')
return
config = Config(version, os.path.dirname(pcb_file_name))
parser = PcbnewParser(pcb_file_name, config, logger, board)
try:
ibom.run_with_dialog(parser, config, logger)
except ParsingException as e:
logger.error(str(e))

View File

@ -0,0 +1,59 @@
import os
import pcbnew
from .xmlparser import XmlParser
from .netlistparser import NetlistParser
PARSERS = {
'.xml': XmlParser,
'.net': NetlistParser,
}
if hasattr(pcbnew, 'FOOTPRINT'):
PARSERS['.kicad_pcb'] = None
def parse_schematic_data(file_name):
if not os.path.isfile(file_name):
return None
extension = os.path.splitext(file_name)[1]
if extension not in PARSERS:
return None
else:
parser_cls = PARSERS[extension]
if parser_cls is None:
return None
parser = parser_cls(file_name)
return parser.get_extra_field_data()
def find_latest_schematic_data(base_name, directories):
"""
:param base_name: base name of pcb file
:param directories: list of directories to search
:return: last modified parsable file path or None if not found
"""
files = []
for d in directories:
files.extend(_find_in_dir(d))
# sort by decreasing modification time
files = sorted(files, reverse=True)
if files:
# try to find first (last modified) file that has name matching pcb file
for _, f in files:
if os.path.splitext(os.path.basename(f))[0] == base_name:
return f
# if no such file is found just return last modified
return files[0][1]
else:
return None
def _find_in_dir(dir):
_, _, files = next(os.walk(dir), (None, None, []))
# filter out files that we can not parse
files = [f for f in files if os.path.splitext(f)[1] in PARSERS.keys()]
files = [os.path.join(dir, f) for f in files]
# get their modification time and sort in descending order
return [(os.path.getmtime(f), f) for f in files]

View File

@ -0,0 +1,52 @@
import io
from .parser_base import ParserBase
from .sexpressions import parse_sexpression
class NetlistParser(ParserBase):
def get_extra_field_data(self):
with io.open(self.file_name, 'r', encoding='utf-8') as f:
sexpression = parse_sexpression(f.read())
components = None
for s in sexpression:
if s[0] == 'components':
components = s[1:]
if components is None:
return None
field_set = set()
comp_dict = {}
for c in components:
ref = None
fields = None
datasheet = None
libsource = None
for f in c[1:]:
if f[0] == 'ref':
ref = f[1]
if f[0] == 'fields':
fields = f[1:]
if f[0] == 'datasheet':
datasheet = f[1]
if f[0] == 'libsource':
libsource = f[1:]
if ref is None:
return None
ref_fields = comp_dict.setdefault(ref, {})
if datasheet and datasheet != '~':
field_set.add('Datasheet')
ref_fields['Datasheet'] = datasheet
if libsource is not None:
for lib_field in libsource:
if lib_field[0] == 'description':
field_set.add('Description')
ref_fields['Description'] = lib_field[1]
if fields is None:
continue
for f in fields:
if len(f) > 1:
field_set.add(f[1][1])
if len(f) > 2:
ref_fields[f[1][1]] = f[2]
return list(field_set), comp_dict

View File

@ -0,0 +1,26 @@
class ParserBase:
def __init__(self, file_name):
"""
:param file_name: path to file that should be parsed.
"""
self.file_name = file_name
def get_extra_field_data(self):
# type: () -> tuple
"""
Parses the file and returns extra field data.
:return: tuple of the format
(
[field_name1, field_name2,... ],
{
ref1: {
field_name1: field_value1,
field_name2: field_value2,
...
],
ref2: ...
}
)
"""
pass

View File

@ -0,0 +1,32 @@
import re
term_regex = r'''(?mx)
\s*(?:
(?P<open>\()|
(?P<close>\))|
(?P<sq>"(?:\\\\|\\"|[^"])*")|
(?P<s>[^(^)\s]+)
)'''
pattern = re.compile(term_regex)
def parse_sexpression(sexpression):
stack = []
out = []
for terms in pattern.finditer(sexpression):
term, value = [(t, v) for t, v in terms.groupdict().items() if v][0]
if term == 'open':
stack.append(out)
out = []
elif term == 'close':
assert stack, "Trouble with nesting of brackets"
tmp, out = out, stack.pop(-1)
out.append(tmp)
elif term == 'sq':
out.append(value[1:-1].replace('\\\\', '\\').replace('\\"', '"'))
elif term == 's':
out.append(value)
else:
raise NotImplementedError("Error: %s, %s" % (term, value))
assert not stack, "Trouble with nesting of brackets"
return out[0]

View File

@ -0,0 +1,38 @@
from xml.dom import minidom
from .parser_base import ParserBase
class XmlParser(ParserBase):
@staticmethod
def get_text(nodelist):
rc = []
for node in nodelist:
if node.nodeType == node.TEXT_NODE:
rc.append(node.data)
return ''.join(rc)
def get_extra_field_data(self):
xml = minidom.parse(self.file_name)
components = xml.getElementsByTagName('comp')
field_set = set()
comp_dict = {}
for c in components:
ref_fields = comp_dict.setdefault(c.attributes['ref'].value, {})
datasheet = c.getElementsByTagName('datasheet')
if datasheet:
datasheet = self.get_text(datasheet[0].childNodes)
if datasheet != '~':
field_set.add('Datasheet')
ref_fields['Datasheet'] = datasheet
libsource = c.getElementsByTagName('libsource')
if libsource and libsource[0].hasAttribute('description'):
field_set.add('Description')
attr = libsource[0].attributes['description']
ref_fields['Description'] = attr.value
for f in c.getElementsByTagName('field'):
name = f.attributes['name'].value
field_set.add(name)
ref_fields[name] = self.get_text(f.childNodes)
return list(field_set), comp_dict

View File

@ -0,0 +1,637 @@
{
"$schema": "http://json-schema.org/draft-06/schema#",
"$ref": "#/definitions/GenericJSONPCBData",
"definitions": {
"GenericJSONPCBData": {
"type": "object",
"additionalProperties": false,
"properties": {
"spec_version": {
"type": "integer"
},
"pcbdata": {
"$ref": "#/definitions/Pcbdata"
},
"components": {
"type": "array",
"items": {
"$ref": "#/definitions/Component"
}
}
},
"required": [
"spec_version",
"pcbdata",
"components"
],
"title": "GenericJSONPCBData"
},
"Component": {
"type": "object",
"additionalProperties": false,
"properties": {
"attr": {
"type": "string"
},
"footprint": {
"type": "string"
},
"layer": {
"$ref": "#/definitions/Layer"
},
"ref": {
"type": "string"
},
"val": {
"type": "string"
},
"extra_fields": {
"$ref": "#/definitions/ExtraData"
}
},
"required": [
"footprint",
"layer",
"ref",
"val"
],
"title": "Component"
},
"Pcbdata": {
"type": "object",
"additionalProperties": false,
"properties": {
"edges_bbox": {
"$ref": "#/definitions/EdgesBbox"
},
"edges": {
"$ref": "#/definitions/DrawingArray"
},
"drawings": {
"$ref": "#/definitions/LayerDrawings"
},
"footprints": {
"type": "array",
"items": {
"$ref": "#/definitions/Footprint"
}
},
"metadata": {
"$ref": "#/definitions/Metadata"
},
"tracks": {
"$ref": "#/definitions/Tracks"
},
"zones": {
"$ref": "#/definitions/Zones"
},
"nets": {
"type": "array",
"items": { "type": "string" }
},
"font_data": {
"$ref": "#/definitions/FontData"
}
},
"required": [
"edges_bbox",
"edges",
"drawings",
"footprints",
"metadata"
],
"dependencies": {
"tracks": { "required": ["zones"] },
"zones": { "required": ["tracks"] }
},
"title": "Pcbdata"
},
"EdgesBbox": {
"type": "object",
"additionalProperties": false,
"properties": {
"minx": {
"type": "number"
},
"miny": {
"type": "number"
},
"maxx": {
"type": "number"
},
"maxy": {
"type": "number"
}
},
"required": ["minx", "miny", "maxx", "maxy"],
"title": "EdgesBbox"
},
"DrawingSet": {
"type": "object",
"additionalProperties": false,
"properties": {
"F": {
"$ref": "#/definitions/DrawingArray"
},
"B": {
"$ref": "#/definitions/DrawingArray"
}
},
"required": ["F", "B"],
"title": "DrawingSet"
},
"Footprint": {
"type": "object",
"additionalProperties": false,
"properties": {
"ref": {
"type": "string"
},
"center": {
"$ref": "#/definitions/Coordinates"
},
"bbox": {
"$ref": "#/definitions/Bbox"
},
"pads": {
"type": "array",
"items": {
"$ref": "#/definitions/Pad"
}
},
"drawings": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"layer": { "$ref": "#/definitions/Layer" },
"drawing": { "$ref": "#/definitions/Drawing" }
},
"required": [ "layer", "drawing" ]
}
},
"layer": {
"$ref": "#/definitions/Layer"
}
},
"required": ["ref", "center", "bbox", "pads", "drawings", "layer"],
"title": "Footprint"
},
"Bbox": {
"type": "object",
"additionalProperties": false,
"properties": {
"pos": {
"$ref": "#/definitions/Coordinates"
},
"relpos": {
"$ref": "#/definitions/Coordinates"
},
"size": {
"$ref": "#/definitions/Coordinates"
},
"angle": {
"type": "number"
}
},
"required": ["pos", "relpos", "size", "angle"],
"title": "Bbox"
},
"Pad": {
"type": "object",
"additionalProperties": false,
"properties": {
"layers": {
"type": "array",
"items": {
"$ref": "#/definitions/Layer"
},
"minItems": 1,
"maxItems": 2
},
"pos": {
"$ref": "#/definitions/Coordinates"
},
"size": {
"$ref": "#/definitions/Coordinates"
},
"angle": {
"type": "number"
},
"shape": {
"$ref": "#/definitions/Shape"
},
"svgpath": { "type": "string" },
"polygons": { "$ref": "#/definitions/Polygons" },
"radius": { "type": "number" },
"chamfpos": { "type": "integer" },
"chamfratio": { "type": "number" },
"type": {
"$ref": "#/definitions/PadType"
},
"pin1": {
"type": "integer", "const": 1
},
"drillshape": {
"$ref": "#/definitions/Drillshape"
},
"drillsize": {
"$ref": "#/definitions/Coordinates"
},
"offset": {
"$ref": "#/definitions/Coordinates"
},
"net": { "type": "string" }
},
"required": [
"layers",
"pos",
"size",
"shape",
"type"
],
"allOf": [
{
"if": {
"properties": { "shape": { "const": "custom" } }
},
"then": {
"anyOf": [
{ "required": [ "svgpath" ] },
{ "required": [ "pos", "angle", "polygons" ] }
]
}
},
{
"if": {
"properties": { "shape": { "const": "roundrect" } }
},
"then": {
"required": [ "radius" ]
}
},
{
"if": {
"properties": { "shape": { "const": "chamfrect" } }
},
"then": {
"required": [ "radius", "chamfpos", "chamfratio" ]
}
},
{
"if": {
"properties": { "type": { "const": "th" } }
},
"then": {
"required": [ "drillshape", "drillsize" ]
}
}
],
"title": "Pad"
},
"Metadata": {
"type": "object",
"additionalProperties": false,
"properties": {
"title": {
"type": "string"
},
"revision": {
"type": "string"
},
"company": {
"type": "string"
},
"date": {
"type": "string"
}
},
"required": ["title", "revision", "company", "date"],
"title": "Metadata"
},
"LayerDrawings": {
"type": "object",
"items": {
"silkscreen": {
"$ref": "#/definitions/DrawingSet"
},
"fabrication": {
"$ref": "#/definitions/DrawingSet"
}
}
},
"DrawingArray": {
"type": "array",
"items": {
"$ref": "#/definitions/Drawing"
}
},
"Drawing": {
"type": "object",
"oneOf": [
{ "$ref": "#/definitions/DrawingSegment" },
{ "$ref": "#/definitions/DrawingRect" },
{ "$ref": "#/definitions/DrawingCircle" },
{ "$ref": "#/definitions/DrawingArc" },
{ "$ref": "#/definitions/DrawingCurve" },
{ "$ref": "#/definitions/DrawingPolygon" },
{ "$ref": "#/definitions/DrawingText" }
]
},
"DrawingSegment": {
"type": "object",
"additionalProperties": false,
"properties": {
"type": { "type": "string", "const": "segment" },
"start": { "$ref": "#/definitions/Coordinates" },
"end": { "$ref": "#/definitions/Coordinates" },
"width": { "type": "number" }
},
"required": ["type", "start", "end", "width"],
"title": "DrawingSegment"
},
"DrawingRect": {
"type": "object",
"additionalProperties": false,
"properties": {
"type": { "const": "rect" },
"start": { "$ref": "#/definitions/Coordinates" },
"end": { "$ref": "#/definitions/Coordinates" },
"width": { "type": "number" }
},
"required": ["type", "start", "end", "width"],
"title": "DrawingRect"
},
"DrawingCircle": {
"type": "object",
"additionalProperties": false,
"properties": {
"type": { "const": "circle" },
"start": { "$ref": "#/definitions/Coordinates" },
"radius": { "type": "number" },
"filled": { "type": "integer" },
"width": { "type": "number" }
},
"required": ["type", "start", "radius", "width"],
"title": "DrawingCircle"
},
"DrawingArc": {
"type": "object",
"additionalProperties": false,
"properties": {
"type": { "const": "arc" },
"width": { "type": "number" },
"svgpath": { "type": "string" },
"start": { "$ref": "#/definitions/Coordinates" },
"radius": { "type": "number" },
"startangle": { "type": "number" },
"endangle": { "type": "number" }
},
"required": [
"type",
"width"
],
"anyOf": [
{ "required": ["svgpath"] },
{ "required": ["start", "radius", "startangle", "endangle"] }
],
"title": "DrawingArc"
},
"DrawingCurve": {
"type": "object",
"additionalProperties": false,
"properties": {
"type": { "const": "curve" },
"start": { "$ref": "#/definitions/Coordinates" },
"end": { "$ref": "#/definitions/Coordinates" },
"cpa": { "$ref": "#/definitions/Coordinates" },
"cpb": { "$ref": "#/definitions/Coordinates" },
"width": { "type": "number" }
},
"required": ["type", "start", "end", "cpa", "cpb", "width"],
"title": "DrawingCurve"
},
"DrawingPolygon": {
"type": "object",
"additionalProperties": false,
"properties": {
"type": { "const": "polygon" },
"filled": { "type": "integer" },
"width": { "type": "number" },
"svgpath": { "type": "string" },
"pos": { "$ref": "#/definitions/Coordinates" },
"angle": { "type": "number" },
"polygons": {
"type": "array",
"items": {
"type": "array",
"items": { "$ref": "#/definitions/Coordinates" }
}
}
},
"required": ["type"],
"anyOf": [
{ "required": ["svgpath"] },
{ "required": ["pos", "angle", "polygons"] }
],
"title": "DrawingPolygon"
},
"DrawingText": {
"type": "object",
"additionalProperties": false,
"properties": {
"svgpath": { "type": "string" },
"thickness": { "type": "number" },
"ref": { "type": "integer" , "const": 1 },
"val": { "type": "integer" , "const": 1 }
},
"required": [
"svgpath",
"thickness"
],
"title": "DrawingText"
},
"Coordinates": {
"type": "array",
"items": { "type": "number" },
"minItems": 2,
"maxItems": 2
},
"Drillshape": {
"type": "string",
"enum": [
"circle",
"oblong"
],
"title": "Drillshape"
},
"Layer": {
"type": "string",
"enum": [
"B",
"F"
],
"title": "Layer"
},
"Shape": {
"type": "string",
"enum": [
"rect",
"circle",
"oval",
"roundrect",
"chamfrect",
"custom"
],
"title": "Shape"
},
"PadType": {
"type": "string",
"enum": [
"smd",
"th"
],
"title": "PadType"
},
"Tracks": {
"type": "object",
"additionalProperties": false,
"properties": {
"F": {
"type": "array",
"items": { "$ref": "#/definitions/Track" }
},
"B": {
"type": "array",
"items": { "$ref": "#/definitions/Track" }
}
},
"required": [ "F", "B" ],
"title": "Tracks"
},
"Track": {
"type": "object",
"oneOf":[
{
"additionalProperties": false,
"properties": {
"start": { "$ref": "#/definitions/Coordinates" },
"end": { "$ref": "#/definitions/Coordinates" },
"width": { "type": "number" },
"net": { "type": "string" }
},
"required": [
"start",
"end",
"width"
]
},
{
"additionalProperties": false,
"properties": {
"center": { "$ref": "#/definitions/Coordinates" },
"startangle": { "type": "number" },
"endangle": { "type": "number" },
"radius": { "type": "number" },
"width": { "type": "number" },
"net": { "type": "string" }
},
"required": [
"center",
"startangle",
"endangle",
"radius",
"width"
]
}
]
},
"Zones": {
"type": "object",
"additionalProperties": false,
"properties": {
"F": {
"type": "array",
"items": { "$ref": "#/definitions/Zone" }
},
"B": {
"type": "array",
"items": { "$ref": "#/definitions/Zone" }
}
},
"required": [ "F", "B" ],
"title": "Zones"
},
"Zone": {
"type": "object",
"additionalProperties": false,
"properties": {
"svgpath": { "type": "string" },
"polygons": {
"$ref": "#/definitions/Polygons"
},
"net": { "type": "string" },
"fillrule": {
"type": "string",
"enum": [
"nonzero",
"evenodd"
]
}
},
"anyOf": [
{ "required": [ "svgpath" ] },
{ "required": [ "polygons" ] }
],
"title": "Zone"
},
"Polygons": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/Coordinates"
}
}
},
"PolyLineArray": {
"$ref": "#/definitions/Polygons"
},
"ReferenceSet": {
"type": "array",
"items": {
"type": "array",
"items": [
{ "type": "string" },
{ "type": "integer" }
],
"additionalItems": false
}
},
"ExtraData": {
"type": "object",
"additionalProperties": true,
"properties": {
},
"title": "ExtraData"
},
"FontData": {
"type": "object",
"patternProperties": {
"^.$" : {
"type": "object",
"properties": {
"w": { "type": "number" },
"l": { "$ref": "#/definitions/PolyLineArray" }
},
"additionalProperties" : false,
"required": [
"w",
"l"
]
}
}
}
}
}

View File

@ -0,0 +1,538 @@
"""This submodule contains very stripped down bare bones version of
svgpathtools module:
https://github.com/mathandy/svgpathtools
All external dependencies are removed. This code can parse path strings with
segments and arcs, calculate bounding box and that's about it. This is all
that is needed in ibom parsers at the moment.
"""
# External dependencies
from __future__ import division, absolute_import, print_function
import re
from cmath import exp
from math import sqrt, cos, sin, acos, degrees, radians, pi
def clip(a, a_min, a_max):
return min(a_max, max(a_min, a))
class Line(object):
def __init__(self, start, end):
self.start = start
self.end = end
def __repr__(self):
return 'Line(start=%s, end=%s)' % (self.start, self.end)
def __eq__(self, other):
if not isinstance(other, Line):
return NotImplemented
return self.start == other.start and self.end == other.end
def __ne__(self, other):
if not isinstance(other, Line):
return NotImplemented
return not self == other
def __len__(self):
return 2
def bbox(self):
"""returns the bounding box for the segment in the form
(xmin, xmax, ymin, ymax)."""
xmin = min(self.start.real, self.end.real)
xmax = max(self.start.real, self.end.real)
ymin = min(self.start.imag, self.end.imag)
ymax = max(self.start.imag, self.end.imag)
return xmin, xmax, ymin, ymax
class Arc(object):
def __init__(self, start, radius, rotation, large_arc, sweep, end,
autoscale_radius=True):
"""
This should be thought of as a part of an ellipse connecting two
points on that ellipse, start and end.
Parameters
----------
start : complex
The start point of the curve. Note: `start` and `end` cannot be the
same. To make a full ellipse or circle, use two `Arc` objects.
radius : complex
rx + 1j*ry, where rx and ry are the radii of the ellipse (also
known as its semi-major and semi-minor axes, or vice-versa or if
rx < ry).
Note: If rx = 0 or ry = 0 then this arc is treated as a
straight line segment joining the endpoints.
Note: If rx or ry has a negative sign, the sign is dropped; the
absolute value is used instead.
Note: If no such ellipse exists, the radius will be scaled so
that one does (unless autoscale_radius is set to False).
rotation : float
This is the CCW angle (in degrees) from the positive x-axis of the
current coordinate system to the x-axis of the ellipse.
large_arc : bool
Given two points on an ellipse, there are two elliptical arcs
connecting those points, the first going the short way around the
ellipse, and the second going the long way around the ellipse. If
`large_arc == False`, the shorter elliptical arc will be used. If
`large_arc == True`, then longer elliptical will be used.
In other words, `large_arc` should be 0 for arcs spanning less than
or equal to 180 degrees and 1 for arcs spanning greater than 180
degrees.
sweep : bool
For any acceptable parameters `start`, `end`, `rotation`, and
`radius`, there are two ellipses with the given major and minor
axes (radii) which connect `start` and `end`. One which connects
them in a CCW fashion and one which connected them in a CW
fashion. If `sweep == True`, the CCW ellipse will be used. If
`sweep == False`, the CW ellipse will be used. See note on curve
orientation below.
end : complex
The end point of the curve. Note: `start` and `end` cannot be the
same. To make a full ellipse or circle, use two `Arc` objects.
autoscale_radius : bool
If `autoscale_radius == True`, then will also scale `self.radius`
in the case that no ellipse exists with the input parameters
(see inline comments for further explanation).
Derived Parameters/Attributes
-----------------------------
self.theta : float
This is the phase (in degrees) of self.u1transform(self.start).
It is $\\theta_1$ in the official documentation and ranges from
-180 to 180.
self.delta : float
This is the angular distance (in degrees) between the start and
end of the arc after the arc has been sent to the unit circle
through self.u1transform().
It is $\\Delta\\theta$ in the official documentation and ranges
from -360 to 360; being positive when the arc travels CCW and
negative otherwise (i.e. is positive/negative when
sweep == True/False).
self.center : complex
This is the center of the arc's ellipse.
self.phi : float
The arc's rotation in radians, i.e. `radians(self.rotation)`.
self.rot_matrix : complex
Equal to `exp(1j * self.phi)` which is also equal to
`cos(self.phi) + 1j*sin(self.phi)`.
Note on curve orientation (CW vs CCW)
-------------------------------------
The notions of clockwise (CW) and counter-clockwise (CCW) are reversed
in some sense when viewing SVGs (as the y coordinate starts at the top
of the image and increases towards the bottom).
"""
assert start != end
assert radius.real != 0 and radius.imag != 0
self.start = start
self.radius = abs(radius.real) + 1j * abs(radius.imag)
self.rotation = rotation
self.large_arc = bool(large_arc)
self.sweep = bool(sweep)
self.end = end
self.autoscale_radius = autoscale_radius
# Convenience parameters
self.phi = radians(self.rotation)
self.rot_matrix = exp(1j * self.phi)
# Derive derived parameters
self._parameterize()
def __repr__(self):
params = (self.start, self.radius, self.rotation,
self.large_arc, self.sweep, self.end)
return ("Arc(start={}, radius={}, rotation={}, "
"large_arc={}, sweep={}, end={})".format(*params))
def __eq__(self, other):
if not isinstance(other, Arc):
return NotImplemented
return self.start == other.start and self.end == other.end \
and self.radius == other.radius \
and self.rotation == other.rotation \
and self.large_arc == other.large_arc and self.sweep == other.sweep
def __ne__(self, other):
if not isinstance(other, Arc):
return NotImplemented
return not self == other
def _parameterize(self):
# See http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
# my notation roughly follows theirs
rx = self.radius.real
ry = self.radius.imag
rx_sqd = rx * rx
ry_sqd = ry * ry
# Transform z-> z' = x' + 1j*y'
# = self.rot_matrix**(-1)*(z - (end+start)/2)
# coordinates. This translates the ellipse so that the midpoint
# between self.end and self.start lies on the origin and rotates
# the ellipse so that the its axes align with the xy-coordinate axes.
# Note: This sends self.end to -self.start
zp1 = (1 / self.rot_matrix) * (self.start - self.end) / 2
x1p, y1p = zp1.real, zp1.imag
x1p_sqd = x1p * x1p
y1p_sqd = y1p * y1p
# Correct out of range radii
# Note: an ellipse going through start and end with radius and phi
# exists if and only if radius_check is true
radius_check = (x1p_sqd / rx_sqd) + (y1p_sqd / ry_sqd)
if radius_check > 1:
if self.autoscale_radius:
rx *= sqrt(radius_check)
ry *= sqrt(radius_check)
self.radius = rx + 1j * ry
rx_sqd = rx * rx
ry_sqd = ry * ry
else:
raise ValueError("No such elliptic arc exists.")
# Compute c'=(c_x', c_y'), the center of the ellipse in (x', y') coords
# Noting that, in our new coord system, (x_2', y_2') = (-x_1', -x_2')
# and our ellipse is cut out by of the plane by the algebraic equation
# (x'-c_x')**2 / r_x**2 + (y'-c_y')**2 / r_y**2 = 1,
# we can find c' by solving the system of two quadratics given by
# plugging our transformed endpoints (x_1', y_1') and (x_2', y_2')
tmp = rx_sqd * y1p_sqd + ry_sqd * x1p_sqd
radicand = (rx_sqd * ry_sqd - tmp) / tmp
try:
radical = sqrt(radicand)
except ValueError:
radical = 0
if self.large_arc == self.sweep:
cp = -radical * (rx * y1p / ry - 1j * ry * x1p / rx)
else:
cp = radical * (rx * y1p / ry - 1j * ry * x1p / rx)
# The center in (x,y) coordinates is easy to find knowing c'
self.center = exp(1j * self.phi) * cp + (self.start + self.end) / 2
# Now we do a second transformation, from (x', y') to (u_x, u_y)
# coordinates, which is a translation moving the center of the
# ellipse to the origin and a dilation stretching the ellipse to be
# the unit circle
u1 = (x1p - cp.real) / rx + 1j * (y1p - cp.imag) / ry
u2 = (-x1p - cp.real) / rx + 1j * (-y1p - cp.imag) / ry
# clip in case of floating point error
u1 = clip(u1.real, -1, 1) + 1j * clip(u1.imag, -1, 1)
u2 = clip(u2.real, -1, 1) + 1j * clip(u2.imag, -1, 1)
# Now compute theta and delta (we'll define them as we go)
# delta is the angular distance of the arc (w.r.t the circle)
# theta is the angle between the positive x'-axis and the start point
# on the circle
if u1.imag > 0:
self.theta = degrees(acos(u1.real))
elif u1.imag < 0:
self.theta = -degrees(acos(u1.real))
else:
if u1.real > 0: # start is on pos u_x axis
self.theta = 0
else: # start is on neg u_x axis
# Note: This behavior disagrees with behavior documented in
# http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
# where theta is set to 0 in this case.
self.theta = 180
det_uv = u1.real * u2.imag - u1.imag * u2.real
acosand = u1.real * u2.real + u1.imag * u2.imag
acosand = clip(acosand.real, -1, 1) + clip(acosand.imag, -1, 1)
if det_uv > 0:
self.delta = degrees(acos(acosand))
elif det_uv < 0:
self.delta = -degrees(acos(acosand))
else:
if u1.real * u2.real + u1.imag * u2.imag > 0:
# u1 == u2
self.delta = 0
else:
# u1 == -u2
# Note: This behavior disagrees with behavior documented in
# http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
# where delta is set to 0 in this case.
self.delta = 180
if not self.sweep and self.delta >= 0:
self.delta -= 360
elif self.large_arc and self.delta <= 0:
self.delta += 360
def point(self, t):
if t == 0:
return self.start
if t == 1:
return self.end
angle = radians(self.theta + t * self.delta)
cosphi = self.rot_matrix.real
sinphi = self.rot_matrix.imag
rx = self.radius.real
ry = self.radius.imag
# z = self.rot_matrix*(rx*cos(angle) + 1j*ry*sin(angle)) + self.center
x = rx * cosphi * cos(angle) - ry * sinphi * sin(
angle) + self.center.real
y = rx * sinphi * cos(angle) + ry * cosphi * sin(
angle) + self.center.imag
return complex(x, y)
def bbox(self):
"""returns a bounding box for the segment in the form
(xmin, xmax, ymin, ymax)."""
# a(t) = radians(self.theta + self.delta*t)
# = (2*pi/360)*(self.theta + self.delta*t)
# x'=0: ~~~~~~~~~
# -rx*cos(phi)*sin(a(t)) = ry*sin(phi)*cos(a(t))
# -(rx/ry)*cot(phi)*tan(a(t)) = 1
# a(t) = arctan(-(ry/rx)tan(phi)) + pi*k === atan_x
# y'=0: ~~~~~~~~~~
# rx*sin(phi)*sin(a(t)) = ry*cos(phi)*cos(a(t))
# (rx/ry)*tan(phi)*tan(a(t)) = 1
# a(t) = arctan((ry/rx)*cot(phi))
# atanres = arctan((ry/rx)*cot(phi)) === atan_y
# ~~~~~~~~
# (2*pi/360)*(self.theta + self.delta*t) = atanres + pi*k
# Therefore, for both x' and y', we have...
# t = ((atan_{x/y} + pi*k)*(360/(2*pi)) - self.theta)/self.delta
# for all k s.t. 0 < t < 1
from math import atan, tan
if cos(self.phi) == 0:
atan_x = pi / 2
atan_y = 0
elif sin(self.phi) == 0:
atan_x = 0
atan_y = pi / 2
else:
rx, ry = self.radius.real, self.radius.imag
atan_x = atan(-(ry / rx) * tan(self.phi))
atan_y = atan((ry / rx) / tan(self.phi))
def angle_inv(ang, q): # inverse of angle from Arc.derivative()
return ((ang + pi * q) * (360 / (2 * pi)) -
self.theta) / self.delta
xtrema = [self.start.real, self.end.real]
ytrema = [self.start.imag, self.end.imag]
for k in range(-4, 5):
tx = angle_inv(atan_x, k)
ty = angle_inv(atan_y, k)
if 0 <= tx <= 1:
xtrema.append(self.point(tx).real)
if 0 <= ty <= 1:
ytrema.append(self.point(ty).imag)
return min(xtrema), max(xtrema), min(ytrema), max(ytrema)
COMMANDS = set('MmZzLlHhVvCcSsQqTtAa')
UPPERCASE = set('MZLHVCSQTA')
COMMAND_RE = re.compile("([MmZzLlHhVvCcSsQqTtAa])")
FLOAT_RE = re.compile(r"[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?")
def _tokenize_path(path_def):
for x in COMMAND_RE.split(path_def):
if x in COMMANDS:
yield x
for token in FLOAT_RE.findall(x):
yield token
def parse_path(pathdef, logger, current_pos=0j):
# In the SVG specs, initial movetos are absolute, even if
# specified as 'm'. This is the default behavior here as well.
# But if you pass in a current_pos variable, the initial moveto
# will be relative to that current_pos. This is useful.
elements = list(_tokenize_path(pathdef))
# Reverse for easy use of .pop()
elements.reverse()
absolute = False
segments = []
start_pos = None
command = None
while elements:
if elements[-1] in COMMANDS:
# New command.
command = elements.pop()
absolute = command in UPPERCASE
command = command.upper()
else:
# If this element starts with numbers, it is an implicit command
# and we don't change the command. Check that it's allowed:
if command is None:
raise ValueError(
"Unallowed implicit command in %s, position %s" % (
pathdef, len(pathdef.split()) - len(elements)))
if command == 'M':
# Moveto command.
x = elements.pop()
y = elements.pop()
pos = float(x) + float(y) * 1j
if absolute:
current_pos = pos
else:
current_pos += pos
# when M is called, reset start_pos
# This behavior of Z is defined in svg spec:
# http://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand
start_pos = current_pos
# Implicit moveto commands are treated as lineto commands.
# So we set command to lineto here, in case there are
# further implicit commands after this moveto.
command = 'L'
elif command == 'Z':
# Close path
if not (current_pos == start_pos):
segments.append(Line(current_pos, start_pos))
current_pos = start_pos
command = None
elif command == 'L':
x = elements.pop()
y = elements.pop()
pos = float(x) + float(y) * 1j
if not absolute:
pos += current_pos
segments.append(Line(current_pos, pos))
current_pos = pos
elif command == 'H':
x = elements.pop()
pos = float(x) + current_pos.imag * 1j
if not absolute:
pos += current_pos.real
segments.append(Line(current_pos, pos))
current_pos = pos
elif command == 'V':
y = elements.pop()
pos = current_pos.real + float(y) * 1j
if not absolute:
pos += current_pos.imag * 1j
segments.append(Line(current_pos, pos))
current_pos = pos
elif command == 'C':
logger.warn('Encountered Cubic Bezier segment. '
'It is currently not supported and will be replaced '
'by a line segment.')
for i in range(4):
# ignore control points
elements.pop()
end = float(elements.pop()) + float(elements.pop()) * 1j
if not absolute:
end += current_pos
segments.append(Line(current_pos, end))
current_pos = end
elif command == 'S':
logger.warn('Encountered Quadratic Bezier segment. '
'It is currently not supported and will be replaced '
'by a line segment.')
for i in range(2):
# ignore control points
elements.pop()
end = float(elements.pop()) + float(elements.pop()) * 1j
if not absolute:
end += current_pos
segments.append(Line(current_pos, end))
current_pos = end
elif command == 'Q':
logger.warn('Encountered Quadratic Bezier segment. '
'It is currently not supported and will be replaced '
'by a line segment.')
for i in range(2):
# ignore control points
elements.pop()
end = float(elements.pop()) + float(elements.pop()) * 1j
if not absolute:
end += current_pos
segments.append(Line(current_pos, end))
current_pos = end
elif command == 'T':
logger.warn('Encountered Quadratic Bezier segment. '
'It is currently not supported and will be replaced '
'by a line segment.')
end = float(elements.pop()) + float(elements.pop()) * 1j
if not absolute:
end += current_pos
segments.append(Line(current_pos, end))
current_pos = end
elif command == 'A':
radius = float(elements.pop()) + float(elements.pop()) * 1j
rotation = float(elements.pop())
arc = float(elements.pop())
sweep = float(elements.pop())
end = float(elements.pop()) + float(elements.pop()) * 1j
if not absolute:
end += current_pos
segments.append(
Arc(current_pos, radius, rotation, arc, sweep, end))
current_pos = end
return segments
def create_path(lines, circles=[]):
"""Returns a path d-string."""
def limit_digits(val):
return format(val, '.6f').rstrip('0').replace(',', '.').rstrip('.')
def different_points(a, b):
return abs(a[0] - b[0]) > 1e-6 or abs(a[1] - b[1]) > 1e-6
parts = []
for i, line in enumerate(lines):
if i == 0 or different_points(lines[i - 1][-1], line[0]):
parts.append('M{},{}'.format(*map(limit_digits, line[0])))
for point in line[1:]:
parts.append('L{},{}'.format(*map(limit_digits, point)))
for circle in circles:
cx, cy, r = circle[0][0], circle[0][1], circle[1]
parts.append('M{},{}'.format(limit_digits(cx - r), limit_digits(cy)))
parts.append('a {},{} 0 1,0 {},0'.format(
*map(limit_digits, [r, r, r + r])))
parts.append('a {},{} 0 1,0 -{},0'.format(
*map(limit_digits, [r, r, r + r])))
return ''.join(parts)

View File

@ -0,0 +1,16 @@
import sys
class ExitCodes():
ERROR_PARSE = 3
ERROR_FILE_NOT_FOUND = 4
ERROR_NO_DISPLAY = 5
class ParsingException(Exception):
pass
def exit_error(logger, code, err):
logger.error(err)
sys.exit(code)

View File

@ -0,0 +1,84 @@
#!/usr/bin/python3
from __future__ import absolute_import
import argparse
import os
import sys
# Add ../ to the path
# Works if this script is executed without installing the module
script_dir = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))
sys.path.insert(0, os.path.dirname(script_dir))
# Pretend we are part of a module
# Avoids: ImportError: attempted relative import with no known parent package
__package__ = os.path.basename(script_dir)
__import__(__package__)
# python 2 and 3 compatibility hack
def to_utf(s):
if isinstance(s, bytes):
return s.decode('utf-8')
else:
return s
def main():
create_wx_app = 'INTERACTIVE_HTML_BOM_NO_DISPLAY' not in os.environ
import wx
if create_wx_app:
app = wx.App()
if hasattr(wx, "APP_ASSERT_SUPPRESS"):
app.SetAssertMode(wx.APP_ASSERT_SUPPRESS)
elif hasattr(wx, "DisableAsserts"):
wx.DisableAsserts()
from .core import ibom
from .core.config import Config
from .ecad import get_parser_by_extension
from .version import version
from .errors import (ExitCodes, ParsingException, exit_error)
parser = argparse.ArgumentParser(
description='KiCad InteractiveHtmlBom plugin CLI.',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('file',
type=lambda s: to_utf(s),
help="KiCad PCB file")
Config.add_options(parser, version)
args = parser.parse_args()
logger = ibom.Logger(cli=True)
if not os.path.isfile(args.file):
exit_error(logger, ExitCodes.ERROR_FILE_NOT_FOUND,
"File %s does not exist." % args.file)
print("Loading %s" % args.file)
config = Config(version, os.path.dirname(os.path.abspath(args.file)))
parser = get_parser_by_extension(
os.path.abspath(args.file), config, logger)
if args.show_dialog:
if not create_wx_app:
exit_error(logger, ExitCodes.ERROR_NO_DISPLAY,
"Can not show dialog when "
"INTERACTIVE_HTML_BOM_NO_DISPLAY is set.")
try:
ibom.run_with_dialog(parser, config, logger)
except ParsingException as e:
exit_error(logger, ExitCodes.ERROR_PARSE, e)
else:
config.set_from_args(args)
try:
ibom.main(parser, config, logger)
except ParsingException as e:
exit_error(logger, ExitCodes.ERROR_PARSE, str(e))
return 0
if __name__ == "__main__":
main()

View File

@ -0,0 +1,12 @@
::start up echo
set i18n_gitAddr= https://github.com/openscopeproject/InteractiveHtmlBom
set i18n_batScar= Bat file by Scarrrr0725
set i18n_thx4using= Thank You For Using Generate Interactive Bom
::convert
set i18n_draghere=Please Drag the EasyEDA PCB source file here :
set i18n_converting=Converting . . . . . .
::converted
set i18n_again=Do you want to convert another file ?
set i18n_converted= EDA source file is converted to bom successfully!

View File

@ -0,0 +1,17 @@
::This file needs to be in 'UTF-8 encoding' AND 'Windows CR LF' to work.
::set active code page as UTF-8/65001
set PYTHONIOENCODING=utf-8
chcp 65001
::start up echo
set i18n_gitAddr= https://github.com/openscopeproject/InteractiveHtmlBom
set i18n_batScar= Bat 文件: Scarrrr0725/XiaoMingXD
set i18n_thx4using= 感谢使用 Generate Interactive Bom
::convert
set i18n_draghere=请将您的 EDA PCB 源文件拖移至此:
set i18n_converting=导出中 . . . . . ."
::converted
set i18n_again=请问是否转换其他文件?
set i18n_converted= 您的 EDA 源文件已成功导出 Bom

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 B

View File

@ -0,0 +1,29 @@
# Update this when new version is tagged.
import os
import subprocess
LAST_TAG = 'v2.6.0'
def _get_git_version():
plugin_path = os.path.realpath(os.path.dirname(__file__))
try:
git_version = subprocess.check_output(
['git', 'describe', '--tags', '--abbrev=4', '--dirty=-*'],
stderr=subprocess.DEVNULL,
cwd=plugin_path)
if isinstance(git_version, bytes):
return git_version.decode('utf-8').rstrip()
else:
return git_version.rstrip()
except subprocess.CalledProcessError:
# print('Git version check failed: ' + str(e))
pass
except Exception:
# print('Git process cannot be launched: ' + str(e))
pass
return None
version = _get_git_version() or LAST_TAG

View File

@ -0,0 +1,805 @@
:root {
--pcb-edge-color: black;
--pad-color: #878787;
--pad-hole-color: #CCCCCC;
--pad-color-highlight: #D04040;
--pad-color-highlight-both: #D0D040;
--pad-color-highlight-marked: #44a344;
--pin1-outline-color: #ffb629;
--pin1-outline-color-highlight: #ffb629;
--pin1-outline-color-highlight-both: #fcbb39;
--pin1-outline-color-highlight-marked: #fdbe41;
--silkscreen-edge-color: #aa4;
--silkscreen-polygon-color: #4aa;
--silkscreen-text-color: #4aa;
--fabrication-edge-color: #907651;
--fabrication-polygon-color: #907651;
--fabrication-text-color: #a27c24;
--track-color: #def5f1;
--track-color-highlight: #D04040;
--zone-color: #def5f1;
--zone-color-highlight: #d0404080;
}
html,
body {
margin: 0px;
height: 100%;
font-family: Verdana, sans-serif;
}
.dark.topmostdiv {
--pcb-edge-color: #eee;
--pad-color: #808080;
--pin1-outline-color: #ffa800;
--pin1-outline-color-highlight: #ccff00;
--track-color: #42524f;
--zone-color: #42524f;
background-color: #252c30;
color: #eee;
}
button {
background-color: #eee;
border: 1px solid #888;
color: black;
height: 44px;
width: 44px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 14px;
font-weight: bolder;
}
.dark button {
/* This will be inverted */
background-color: #c3b7b5;
}
button.depressed {
background-color: #0a0;
color: white;
}
.dark button.depressed {
/* This will be inverted */
background-color: #b3b;
}
button:focus {
outline: 0;
}
button#tb-btn {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8.47 8.47'%3E%3Crect transform='translate(0 -288.53)' ry='1.17' y='288.8' x='.27' height='7.94' width='7.94' fill='%23f9f9f9'/%3E%3Cg transform='translate(0 -288.53)'%3E%3Crect width='7.94' height='7.94' x='.27' y='288.8' ry='1.17' fill='none' stroke='%23000' stroke-width='.4' stroke-linejoin='round'/%3E%3Cpath d='M1.32 290.12h5.82M1.32 291.45h5.82' fill='none' stroke='%23000' stroke-width='.4'/%3E%3Cpath d='M4.37 292.5v4.23M.26 292.63H8.2' fill='none' stroke='%23000' stroke-width='.3'/%3E%3Ctext font-weight='700' font-size='3.17' font-family='sans-serif'%3E%3Ctspan x='1.35' y='295.73'%3EF%3C/tspan%3E%3Ctspan x='5.03' y='295.68'%3EB%3C/tspan%3E%3C/text%3E%3C/g%3E%3C/svg%3E%0A");
}
button#lr-btn {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8.47 8.47'%3E%3Crect transform='translate(0 -288.53)' ry='1.17' y='288.8' x='.27' height='7.94' width='7.94' fill='%23f9f9f9'/%3E%3Cg transform='translate(0 -288.53)'%3E%3Crect width='7.94' height='7.94' x='.27' y='288.8' ry='1.17' fill='none' stroke='%23000' stroke-width='.4' stroke-linejoin='round'/%3E%3Cpath d='M1.06 290.12H3.7m-2.64 1.33H3.7m-2.64 1.32H3.7m-2.64 1.3H3.7m-2.64 1.33H3.7' fill='none' stroke='%23000' stroke-width='.4'/%3E%3Cpath d='M4.37 288.8v7.94m0-4.11h3.96' fill='none' stroke='%23000' stroke-width='.3'/%3E%3Ctext font-weight='700' font-size='3.17' font-family='sans-serif'%3E%3Ctspan x='5.11' y='291.96'%3EF%3C/tspan%3E%3Ctspan x='5.03' y='295.68'%3EB%3C/tspan%3E%3C/text%3E%3C/g%3E%3C/svg%3E%0A");
}
button#bom-btn {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8.47 8.47'%3E%3Crect transform='translate(0 -288.53)' ry='1.17' y='288.8' x='.27' height='7.94' width='7.94' fill='%23f9f9f9'/%3E%3Cg transform='translate(0 -288.53)' fill='none' stroke='%23000' stroke-width='.4'%3E%3Crect width='7.94' height='7.94' x='.27' y='288.8' ry='1.17' stroke-linejoin='round'/%3E%3Cpath d='M1.59 290.12h5.29M1.59 291.45h5.33M1.59 292.75h5.33M1.59 294.09h5.33M1.59 295.41h5.33'/%3E%3C/g%3E%3C/svg%3E");
}
button#bom-grouped-btn {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cg stroke='%23000' stroke-linejoin='round' class='layer'%3E%3Crect width='29' height='29' x='1.5' y='1.5' stroke-width='2' fill='%23fff' rx='5' ry='5'/%3E%3Cpath stroke-linecap='square' stroke-width='2' d='M6 10h4m4 0h5m4 0h3M6.1 22h3m3.9 0h5m4 0h4m-16-8h4m4 0h4'/%3E%3Cpath stroke-linecap='null' d='M5 17.5h22M5 26.6h22M5 5.5h22'/%3E%3C/g%3E%3C/svg%3E");
}
button#bom-ungrouped-btn {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cg stroke='%23000' stroke-linejoin='round' class='layer'%3E%3Crect width='29' height='29' x='1.5' y='1.5' stroke-width='2' fill='%23fff' rx='5' ry='5'/%3E%3Cpath stroke-linecap='square' stroke-width='2' d='M6 10h4m-4 8h3m-3 8h4'/%3E%3Cpath stroke-linecap='null' d='M5 13.5h22m-22 8h22M5 5.5h22'/%3E%3C/g%3E%3C/svg%3E");
}
button#bom-netlist-btn {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cg fill='none' stroke='%23000' class='layer'%3E%3Crect width='29' height='29' x='1.5' y='1.5' stroke-width='2' fill='%23fff' rx='5' ry='5'/%3E%3Cpath stroke-width='2' d='M6 26l6-6v-8m13.8-6.3l-6 6v8'/%3E%3Ccircle cx='11.8' cy='9.5' r='2.8' stroke-width='2'/%3E%3Ccircle cx='19.8' cy='22.8' r='2.8' stroke-width='2'/%3E%3C/g%3E%3C/svg%3E");
}
button#copy {
background-image: url("data:image/svg+xml,%3Csvg height='48' viewBox='0 0 48 48' width='48' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0h48v48h-48z' fill='none'/%3E%3Cpath d='M32 2h-24c-2.21 0-4 1.79-4 4v28h4v-28h24v-4zm6 8h-22c-2.21 0-4 1.79-4 4v28c0 2.21 1.79 4 4 4h22c2.21 0 4-1.79 4-4v-28c0-2.21-1.79-4-4-4zm0 32h-22v-28h22v28z'/%3E%3C/svg%3E");
background-position: 6px 6px;
background-repeat: no-repeat;
background-size: 26px 26px;
border-radius: 6px;
height: 40px;
width: 40px;
margin: 10px 5px;
}
button#copy:active {
box-shadow: inset 0px 0px 5px #6c6c6c;
}
textarea.clipboard-temp {
position: fixed;
top: 0;
left: 0;
width: 2em;
height: 2em;
padding: 0;
border: None;
outline: None;
box-shadow: None;
background: transparent;
}
.left-most-button {
border-right: 0;
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
}
.middle-button {
border-right: 0;
}
.right-most-button {
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
}
.button-container {
font-size: 0;
margin: 10px 10px 10px 0px;
}
.dark .button-container {
filter: invert(1);
}
.button-container button {
background-size: 32px 32px;
background-position: 5px 5px;
background-repeat: no-repeat;
}
@media print {
.hideonprint {
display: none;
}
}
canvas {
cursor: crosshair;
}
canvas:active {
cursor: grabbing;
}
.fileinfo {
width: 100%;
max-width: 1000px;
border: none;
padding: 5px;
}
.fileinfo .title {
font-size: 20pt;
font-weight: bold;
}
.fileinfo td {
overflow: hidden;
white-space: nowrap;
max-width: 1px;
width: 50%;
text-overflow: ellipsis;
}
.bom {
border-collapse: collapse;
font-family: Consolas, "DejaVu Sans Mono", Monaco, monospace;
font-size: 10pt;
table-layout: fixed;
width: 100%;
margin-top: 1px;
position: relative;
}
.bom th,
.bom td {
border: 1px solid black;
padding: 5px;
word-wrap: break-word;
text-align: center;
position: relative;
}
.dark .bom th,
.dark .bom td {
border: 1px solid #777;
}
.bom th {
background-color: #CCCCCC;
background-clip: padding-box;
}
.dark .bom th {
background-color: #3b4749;
}
.bom tr.highlighted:nth-child(n) {
background-color: #cfc;
}
.dark .bom tr.highlighted:nth-child(n) {
background-color: #226022;
}
.bom tr:nth-child(even) {
background-color: #f2f2f2;
}
.dark .bom tr:nth-child(even) {
background-color: #313b40;
}
.bom tr.checked {
color: #1cb53d;
}
.dark .bom tr.checked {
color: #2cce54;
}
.bom tr {
transition: background-color 0.2s;
}
.bom .numCol {
width: 30px;
}
.bom .value {
width: 15%;
}
.bom .quantity {
width: 65px;
}
.bom th .sortmark {
position: absolute;
right: 1px;
top: 1px;
margin-top: -5px;
border-width: 5px;
border-style: solid;
border-color: transparent transparent #221 transparent;
transform-origin: 50% 85%;
transition: opacity 0.2s, transform 0.4s;
}
.dark .bom th .sortmark {
filter: invert(1);
}
.bom th .sortmark.none {
opacity: 0;
}
.bom th .sortmark.desc {
transform: rotate(180deg);
}
.bom th:hover .sortmark.none {
opacity: 0.5;
}
.bom .bom-checkbox {
width: 30px;
position: relative;
user-select: none;
-moz-user-select: none;
}
.bom .bom-checkbox:before {
content: "";
position: absolute;
border-width: 15px;
border-style: solid;
border-color: #51829f transparent transparent transparent;
visibility: hidden;
top: -15px;
}
.bom .bom-checkbox:after {
content: "Double click to set/unset all";
position: absolute;
color: white;
top: -35px;
left: -26px;
background: #51829f;
padding: 5px 15px;
border-radius: 8px;
white-space: nowrap;
visibility: hidden;
}
.bom .bom-checkbox:hover:before,
.bom .bom-checkbox:hover:after {
visibility: visible;
transition: visibility 0.2s linear 1s;
}
.split {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
overflow-y: auto;
overflow-x: hidden;
background-color: inherit;
}
.split.split-horizontal,
.gutter.gutter-horizontal {
height: 100%;
float: left;
}
.gutter {
background-color: #ddd;
background-repeat: no-repeat;
background-position: 50%;
transition: background-color 0.3s;
}
.dark .gutter {
background-color: #777;
}
.gutter.gutter-horizontal {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==');
cursor: ew-resize;
width: 5px;
}
.gutter.gutter-vertical {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII=');
cursor: ns-resize;
height: 5px;
}
.searchbox {
float: left;
height: 40px;
margin: 10px 5px;
padding: 12px 32px;
font-family: Consolas, "DejaVu Sans Mono", Monaco, monospace;
font-size: 18px;
box-sizing: border-box;
border: 1px solid #888;
border-radius: 6px;
outline: none;
background-color: #eee;
transition: background-color 0.2s, border 0.2s;
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABNklEQVQ4T8XSMUvDQBQH8P/LElFa/AIZHcTBQSz0I/gFstTBRR2KUC4ldDxw7h0Bl3RRUATxi4iiODgoiLNrbQYp5J6cpJJqomkX33Z37/14d/dIa33MzDuYI4johOI4XhyNRteO46zNYjDzAxE1yBZprVeZ+QbAUhXEGJMA2Ox2u4+fQIa0mPmsCgCgJYQ4t7lfgF0opQYAdv9ABkKI/UnOFCClXKjX61cA1osQY8x9kiRNKeV7IWA3oyhaSdP0FkAtjxhj3hzH2RBCPOf3pzqYHCilfAAX+URm9oMguPzeWSGQvUcMYC8rOBJCHBRdqxTo9/vbRHRqi8bj8XKv1xvODbiuW2u32/bvf0SlDv4XYOY7z/Mavu+nM1+BmQ+NMc0wDF/LprP0DbTWW0T00ul0nn4b7Q87+X4Qmfiq2wAAAABJRU5ErkJggg==');
background-position: 10px 10px;
background-repeat: no-repeat;
}
.dark .searchbox {
background-color: #111;
color: #eee;
}
.searchbox::placeholder {
color: #ccc;
}
.dark .searchbox::placeholder {
color: #666;
}
.filter {
width: calc(60% - 64px);
}
.reflookup {
width: calc(40% - 10px);
}
input[type=text]:focus {
background-color: white;
border: 1px solid #333;
}
.dark input[type=text]:focus {
background-color: #333;
border: 1px solid #ccc;
}
mark.highlight {
background-color: #5050ff;
color: #fff;
padding: 2px;
border-radius: 6px;
}
.dark mark.highlight {
background-color: #76a6da;
color: #111;
}
.menubtn {
background-color: white;
border: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='36' viewBox='0 0 20 20'%3E%3Cpath fill='none' d='M0 0h20v20H0V0z'/%3E%3Cpath d='M15.95 10.78c.03-.25.05-.51.05-.78s-.02-.53-.06-.78l1.69-1.32c.15-.12.19-.34.1-.51l-1.6-2.77c-.1-.18-.31-.24-.49-.18l-1.99.8c-.42-.32-.86-.58-1.35-.78L12 2.34c-.03-.2-.2-.34-.4-.34H8.4c-.2 0-.36.14-.39.34l-.3 2.12c-.49.2-.94.47-1.35.78l-1.99-.8c-.18-.07-.39 0-.49.18l-1.6 2.77c-.1.18-.06.39.1.51l1.69 1.32c-.04.25-.07.52-.07.78s.02.53.06.78L2.37 12.1c-.15.12-.19.34-.1.51l1.6 2.77c.1.18.31.24.49.18l1.99-.8c.42.32.86.58 1.35.78l.3 2.12c.04.2.2.34.4.34h3.2c.2 0 .37-.14.39-.34l.3-2.12c.49-.2.94-.47 1.35-.78l1.99.8c.18.07.39 0 .49-.18l1.6-2.77c.1-.18.06-.39-.1-.51l-1.67-1.32zM10 13c-1.65 0-3-1.35-3-3s1.35-3 3-3 3 1.35 3 3-1.35 3-3 3z'/%3E%3C/svg%3E%0A");
background-position: center;
background-repeat: no-repeat;
}
.statsbtn {
background-color: white;
border: none;
background-image: url("data:image/svg+xml,%3Csvg width='36' height='36' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 6h28v24H4V6zm0 8h28v8H4m9-16v24h10V5.8' fill='none' stroke='%23000' stroke-width='2'/%3E%3C/svg%3E");
background-position: center;
background-repeat: no-repeat;
}
.iobtn {
background-color: white;
border: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='36'%3E%3Cpath fill='none' stroke='%23000' stroke-width='2' d='M3 33v-7l6.8-7h16.5l6.7 7v7H3zM3.2 26H33M21 9l5-5.9 5 6h-2.5V15h-5V9H21zm-4.9 0l-5 6-5-6h2.5V3h5v6h2.5z'/%3E%3Cpath fill='none' stroke='%23000' d='M6.1 29.5H10'/%3E%3C/svg%3E");
background-position: center;
background-repeat: no-repeat;
}
.visbtn {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath fill='none' stroke='%23333' d='M2.5 4.5h5v15h-5zM9.5 4.5h5v15h-5zM16.5 4.5h5v15h-5z'/%3E%3C/svg%3E");
background-position: center;
background-repeat: no-repeat;
padding: 15px;
}
#vismenu-content {
left: 0px;
font-family: Verdana, sans-serif;
}
.dark .statsbtn,
.dark .savebtn,
.dark .menubtn,
.dark .iobtn,
.dark .visbtn {
filter: invert(1);
}
.flexbox {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.savebtn {
background-color: #d6d6d6;
width: auto;
height: 30px;
flex-grow: 1;
margin: 5px;
border-radius: 4px;
}
.savebtn:active {
background-color: #0a0;
color: white;
}
.dark .savebtn:active {
/* This will be inverted */
background-color: #b3b;
}
.stats {
border-collapse: collapse;
font-size: 12pt;
table-layout: fixed;
width: 100%;
min-width: 450px;
}
.dark .stats td {
border: 1px solid #bbb;
}
.stats td {
border: 1px solid black;
padding: 5px;
word-wrap: break-word;
text-align: center;
position: relative;
}
#checkbox-stats div {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
#checkbox-stats .bar {
background-color: rgba(28, 251, 0, 0.6);
}
.menu {
position: relative;
display: inline-block;
margin: 10px 10px 10px 0px;
}
.menu-content {
font-size: 12pt !important;
text-align: left !important;
font-weight: normal !important;
display: none;
position: absolute;
background-color: white;
right: 0;
min-width: 300px;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
z-index: 100;
padding: 8px;
}
.dark .menu-content {
background-color: #111;
}
.menu:hover .menu-content {
display: block;
}
.menu:hover .menubtn,
.menu:hover .iobtn,
.menu:hover .statsbtn {
background-color: #eee;
}
.menu-label {
display: inline-block;
padding: 8px;
border: 1px solid #ccc;
border-top: 0;
width: calc(100% - 18px);
}
.menu-label-top {
border-top: 1px solid #ccc;
}
.menu-textbox {
float: left;
height: 24px;
margin: 10px 5px;
padding: 5px 5px;
font-family: Consolas, "DejaVu Sans Mono", Monaco, monospace;
font-size: 14px;
box-sizing: border-box;
border: 1px solid #888;
border-radius: 4px;
outline: none;
background-color: #eee;
transition: background-color 0.2s, border 0.2s;
width: calc(100% - 10px);
}
.menu-textbox.invalid,
.dark .menu-textbox.invalid {
color: red;
}
.dark .menu-textbox {
background-color: #222;
color: #eee;
}
.radio-container {
margin: 4px;
}
.topmostdiv {
width: 100%;
height: 100%;
background-color: white;
transition: background-color 0.3s;
}
#top {
height: 78px;
border-bottom: 2px solid black;
}
.dark #top {
border-bottom: 2px solid #ccc;
}
#dbg {
display: block;
}
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #aaa;
}
::-webkit-scrollbar-thumb {
background: #666;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}
.slider {
-webkit-appearance: none;
width: 100%;
margin: 3px 0;
padding: 0;
outline: none;
opacity: 0.7;
-webkit-transition: .2s;
transition: opacity .2s;
border-radius: 3px;
}
.slider:hover {
opacity: 1;
}
.slider:focus {
outline: none;
}
.slider::-webkit-slider-runnable-track {
-webkit-appearance: none;
width: 100%;
height: 8px;
background: #d3d3d3;
border-radius: 3px;
border: none;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 15px;
height: 15px;
border-radius: 50%;
background: #0a0;
cursor: pointer;
margin-top: -4px;
}
.dark .slider::-webkit-slider-thumb {
background: #3d3;
}
.slider::-moz-range-thumb {
width: 15px;
height: 15px;
border-radius: 50%;
background: #0a0;
cursor: pointer;
}
.slider::-moz-range-track {
height: 8px;
background: #d3d3d3;
border-radius: 3px;
}
.dark .slider::-moz-range-thumb {
background: #3d3;
}
.slider::-ms-track {
width: 100%;
height: 8px;
border-width: 3px 0;
background: transparent;
border-color: transparent;
color: transparent;
transition: opacity .2s;
}
.slider::-ms-fill-lower {
background: #d3d3d3;
border: none;
border-radius: 3px;
}
.slider::-ms-fill-upper {
background: #d3d3d3;
border: none;
border-radius: 3px;
}
.slider::-ms-thumb {
width: 15px;
height: 15px;
border-radius: 50%;
background: #0a0;
cursor: pointer;
margin: 0;
}
.shameless-plug {
font-size: 0.8em;
text-align: center;
display: block;
}
a {
color: #0278a4;
}
.dark a {
color: #00b9fd;
}
#frontcanvas,
#backcanvas {
touch-action: none;
}
.placeholder {
border: 1px dashed #9f9fda !important;
background-color: #edf2f7 !important;
}
.dragging {
z-index: 999;
}
.dark .dragging>table>tbody>tr {
background-color: #252c30;
}
.dark .placeholder {
filter: invert(1);
}
.column-spacer {
top: 0;
left: 0;
width: calc(100% - 4px);
position: absolute;
cursor: pointer;
user-select: none;
height: 100%;
}
.column-width-handle {
top: 0;
right: 0;
width: 4px;
position: absolute;
cursor: col-resize;
user-select: none;
height: 100%;
}
.column-width-handle:hover {
background-color: #4f99bd;
}
.help-link {
border: 1px solid #0278a4;
padding-inline: 0.3rem;
border-radius: 3px;
cursor: pointer;
}
.dark .help-link {
border: 1px solid #00b9fd;
}

View File

@ -0,0 +1,319 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interactive BOM for KiCAD</title>
<style type="text/css">
///CSS///
///USERCSS///
</style>
<script type="text/javascript" >
///////////////////////////////////////////////
///SPLITJS///
///////////////////////////////////////////////
///////////////////////////////////////////////
///LZ-STRING///
///////////////////////////////////////////////
///////////////////////////////////////////////
///POINTER_EVENTS_POLYFILL///
///////////////////////////////////////////////
///////////////////////////////////////////////
///CONFIG///
///////////////////////////////////////////////
///////////////////////////////////////////////
///PCBDATA///
///////////////////////////////////////////////
///////////////////////////////////////////////
///UTILJS///
///////////////////////////////////////////////
///////////////////////////////////////////////
///RENDERJS///
///////////////////////////////////////////////
///////////////////////////////////////////////
///TABLEUTILJS///
///////////////////////////////////////////////
///////////////////////////////////////////////
///IBOMJS///
///////////////////////////////////////////////
///////////////////////////////////////////////
///USERJS///
///////////////////////////////////////////////
</script>
</head>
<body>
///USERHEADER///
<div id="topmostdiv" class="topmostdiv">
<div id="top">
<div style="float: right; height: 100%;">
<div class="hideonprint menu" style="float: right; top: 8px;">
<button class="menubtn"></button>
<div class="menu-content">
<label class="menu-label menu-label-top" style="width: calc(50% - 18px)">
<input id="darkmodeCheckbox" type="checkbox" onchange="setDarkMode(this.checked)">
Dark mode
</label><!-- This comment eats space! All of it!
--><label class="menu-label menu-label-top" style="width: calc(50% - 17px); border-left: 0;">
<input id="fullscreenCheckbox" type="checkbox" onchange="setFullscreen(this.checked)">
Full Screen
</label>
<label class="menu-label" style="width: calc(50% - 18px)">
<input id="fabricationCheckbox" type="checkbox" checked onchange="fabricationVisible(this.checked)">
Fab layer
</label><!-- This comment eats space! All of it!
--><label class="menu-label" style="width: calc(50% - 17px); border-left: 0;">
<input id="silkscreenCheckbox" type="checkbox" checked onchange="silkscreenVisible(this.checked)">
Silkscreen
</label>
<label class="menu-label" style="width: calc(50% - 18px)">
<input id="referencesCheckbox" type="checkbox" checked onchange="referencesVisible(this.checked)">
References
</label><!-- This comment eats space! All of it!
--><label class="menu-label" style="width: calc(50% - 17px); border-left: 0;">
<input id="valuesCheckbox" type="checkbox" checked onchange="valuesVisible(this.checked)">
Values
</label>
<div id="tracksAndZonesCheckboxes">
<label class="menu-label" style="width: calc(50% - 18px)">
<input id="tracksCheckbox" type="checkbox" checked onchange="tracksVisible(this.checked)">
Tracks
</label><!-- This comment eats space! All of it!
--><label class="menu-label" style="width: calc(50% - 17px); border-left: 0;">
<input id="zonesCheckbox" type="checkbox" checked onchange="zonesVisible(this.checked)">
Zones
</label>
</div>
<label class="menu-label" style="width: calc(50% - 18px)">
<input id="padsCheckbox" type="checkbox" checked onchange="padsVisible(this.checked)">
Pads
</label><!-- This comment eats space! All of it!
--><label class="menu-label" style="width: calc(50% - 17px); border-left: 0;">
<input id="dnpOutlineCheckbox" type="checkbox" checked onchange="dnpOutline(this.checked)">
DNP outlined
</label>
<label class="menu-label">
<input id="highlightpin1Checkbox" type="checkbox" onchange="setHighlightPin1(this.checked)">
Highlight first pin
</label>
<label class="menu-label">
<input id="dragCheckbox" type="checkbox" checked onchange="setRedrawOnDrag(this.checked)">
Continuous redraw on drag
</label>
<label class="menu-label">
<span>Board rotation</span>
<span style="float: right"><span id="rotationDegree">0</span>&#176;</span>
<input id="boardRotation" type="range" min="-36" max="36" value="0" class="slider" oninput="setBoardRotation(this.value)">
</label>
<label class="menu-label">
<input id="offsetBackRotationCheckbox" type="checkbox" onchange="setOffsetBackRotation(this.checked)">
Offset back rotation
</label>
<label class="menu-label">
<div style="margin-left: 5px">Bom checkboxes</div>
<input id="bomCheckboxes" class="menu-textbox" type=text
oninput="setBomCheckboxes(this.value)">
</label>
<label class="menu-label">
<div style="margin-left: 5px">Mark when checked</div>
<div id="markWhenCheckedContainer"></div>
</label>
<label class="menu-label">
<span class="shameless-plug">
<span>Created using</span>
<a id="github-link" target="blank" href="https://github.com/openscopeproject/InteractiveHtmlBom">InteractiveHtmlBom</a>
<a target="blank" title="Mouse and keyboard help" href="https://github.com/openscopeproject/InteractiveHtmlBom/wiki/Usage#bom-page-mouse-actions" style="text-decoration: none;"><label class="help-link">?</label></a>
</span>
</label>
</div>
</div>
<div class="button-container hideonprint"
style="float: right; position: relative; top: 8px">
<button id="fl-btn" class="left-most-button" onclick="changeCanvasLayout('F')"
title="Front only">F
</button>
<button id="fb-btn" class="middle-button" onclick="changeCanvasLayout('FB')"
title="Front and Back">FB
</button>
<button id="bl-btn" class="right-most-button" onclick="changeCanvasLayout('B')"
title="Back only">B
</button>
</div>
<div class="button-container hideonprint"
style="float: right; position: relative; top: 8px">
<button id="bom-btn" class="left-most-button" onclick="changeBomLayout('bom-only')"
title="BOM only"></button>
<button id="lr-btn" class="middle-button" onclick="changeBomLayout('left-right')"
title="BOM left, drawings right"></button>
<button id="tb-btn" class="right-most-button" onclick="changeBomLayout('top-bottom')"
title="BOM top, drawings bot"></button>
</div>
<div class="button-container hideonprint"
style="float: right; position: relative; top: 8px">
<button id="bom-grouped-btn" class="left-most-button" onclick="changeBomMode('grouped')"
title="Grouped BOM"></button>
<button id="bom-ungrouped-btn" class="middle-button" onclick="changeBomMode('ungrouped')"
title="Ungrouped BOM"></button>
<button id="bom-netlist-btn" class="right-most-button" onclick="changeBomMode('netlist')"
title="Netlist"></button>
</div>
<div class="hideonprint menu" style="float: right; top: 8px;">
<button class="statsbtn"></button>
<div class="menu-content">
<table class="stats">
<tbody>
<tr>
<td width="40%">Board stats</td>
<td>Front</td>
<td>Back</td>
<td>Total</td>
</tr>
<tr>
<td>Components</td>
<td id="stats-components-front">~</td>
<td id="stats-components-back">~</td>
<td id="stats-components-total">~</td>
</tr>
<tr>
<td>Groups</td>
<td id="stats-groups-front">~</td>
<td id="stats-groups-back">~</td>
<td id="stats-groups-total">~</td>
</tr>
<tr>
<td>SMD pads</td>
<td id="stats-smd-pads-front">~</td>
<td id="stats-smd-pads-back">~</td>
<td id="stats-smd-pads-total">~</td>
</tr>
<tr>
<td>TH pads</td>
<td colspan=3 id="stats-th-pads">~</td>
</tr>
</tbody>
</table>
<table class="stats">
<col width="40%"/><col />
<tbody id="checkbox-stats">
<tr>
<td colspan=2 style="border-top: 0">Checkboxes</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="hideonprint menu" style="float: right; top: 8px;">
<button class="iobtn"></button>
<div class="menu-content">
<div class="menu-label menu-label-top">
<div style="margin-left: 5px;">Save board image</div>
<div class="flexbox">
<input id="render-save-width" class="menu-textbox" type="text" value="1000" placeholder="Width"
style="flex-grow: 1; width: 50px;" oninput="validateSaveImgDimension(this)">
<span>X</span>
<input id="render-save-height" class="menu-textbox" type="text" value="1000" placeholder="Height"
style="flex-grow: 1; width: 50px;" oninput="validateSaveImgDimension(this)">
</div>
<label>
<input id="render-save-transparent" type="checkbox">
Transparent background
</label>
<div class="flexbox">
<button class="savebtn" onclick="saveImage('F')">Front</button>
<button class="savebtn" onclick="saveImage('B')">Back</button>
</div>
</div>
<div class="menu-label">
<span style="margin-left: 5px;">Config and checkbox state</span>
<div class="flexbox">
<button class="savebtn" onclick="saveSettings()">Export</button>
<button class="savebtn" onclick="loadSettings()">Import</button>
</div>
</div>
<div class="menu-label">
<span style="margin-left: 5px;">Save bom table as</span>
<div class="flexbox">
<button class="savebtn" onclick="saveBomTable('csv')">csv</button>
<button class="savebtn" onclick="saveBomTable('txt')">txt</button>
</div>
</div>
</div>
</div>
</div>
<div id="fileinfodiv" style="overflow: auto;">
<table class="fileinfo">
<tbody>
<tr>
<td id="title" class="title" style="width: 70%">
Title
</td>
<td id="revision" class="title" style="width: 30%">
Revision
</td>
</tr>
<tr>
<td id="company">
Company
</td>
<td id="filedate">
Date
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div id="bot" class="split" style="height: calc(100% - 80px)">
<div id="bomdiv" class="split split-horizontal">
<div style="width: 100%">
<input id="reflookup" class="textbox searchbox reflookup hideonprint" type="text" placeholder="Ref lookup"
oninput="updateRefLookup(this.value)">
<input id="filter" class="textbox searchbox filter hideonprint" type="text" placeholder="Filter"
oninput="updateFilter(this.value)">
<div class="button-container hideonprint" style="float: left; margin: 0;">
<button id="copy" title="Copy bom table to clipboard"
onclick="saveBomTable('clipboard')"></button>
</div>
</div>
<div id="dbg"></div>
<table class="bom" id="bomtable">
<thead id="bomhead">
</thead>
<tbody id="bombody">
</tbody>
</table>
</div>
<div id="canvasdiv" class="split split-horizontal">
<div id="frontcanvas" class="split" touch-action="none" style="overflow: hidden">
<div style="position: relative; width: 100%; height: 100%;">
<canvas id="F_bg" style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
<canvas id="F_fab" style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
<canvas id="F_slk" style="position: absolute; left: 0; top: 0; z-index: 2;"></canvas>
<canvas id="F_hl" style="position: absolute; left: 0; top: 0; z-index: 3;"></canvas>
</div>
</div>
<div id="backcanvas" class="split" touch-action="none" style="overflow: hidden">
<div style="position: relative; width: 100%; height: 100%;">
<canvas id="B_bg" style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
<canvas id="B_fab" style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
<canvas id="B_slk" style="position: absolute; left: 0; top: 0; z-index: 2;"></canvas>
<canvas id="B_hl" style="position: absolute; left: 0; top: 0; z-index: 3;"></canvas>
</div>
</div>
</div>
</div>
</div>
///USERFOOTER///
</body>
</html>

View File

@ -0,0 +1,10 @@
// Copyright (c) 2013 Pieroxy <pieroxy@pieroxy.net>
// This work is free. You can redistribute it and/or modify it
// under the terms of the WTFPL, Version 2
// For more information see LICENSE.txt or http://www.wtfpl.net/
//
// For more information, the home page:
// http://pieroxy.net/blog/pages/lz-string/testing.html
//
// LZ-based compression algorithm, version 1.4.4
var LZString=function(){var o=String.fromCharCode,i={};var n={decompressFromBase64:function(o){return null==o?"":""==o?null:n._decompress(o.length,32,function(n){return function(o,n){if(!i[o]){i[o]={};for(var t=0;t<o.length;t++)i[o][o.charAt(t)]=t}return i[o][n]}("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",o.charAt(n))})},_decompress:function(i,n,t){var r,e,a,s,p,u,l,f=[],c=4,d=4,h=3,v="",g=[],m={val:t(0),position:n,index:1};for(r=0;r<3;r+=1)f[r]=r;for(a=0,p=Math.pow(2,2),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;switch(a){case 0:for(a=0,p=Math.pow(2,8),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;l=o(a);break;case 1:for(a=0,p=Math.pow(2,16),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;l=o(a);break;case 2:return""}for(f[3]=l,e=l,g.push(l);;){if(m.index>i)return"";for(a=0,p=Math.pow(2,h),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;switch(l=a){case 0:for(a=0,p=Math.pow(2,8),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;f[d++]=o(a),l=d-1,c--;break;case 1:for(a=0,p=Math.pow(2,16),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;f[d++]=o(a),l=d-1,c--;break;case 2:return g.join("")}if(0==c&&(c=Math.pow(2,h),h++),f[l])v=f[l];else{if(l!==d)return null;v=e+e.charAt(0)}g.push(v),f[d++]=e+v.charAt(0),e=v,0==--c&&(c=Math.pow(2,h),h++)}}};return n}();"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module?module.exports=LZString:"undefined"!=typeof angular&&null!=angular&&angular.module("LZString",[]).factory("LZString",function(){return LZString});

View File

@ -0,0 +1,43 @@
/*!
* PEP v0.4.3 | https://github.com/jquery/PEP
* Copyright jQuery Foundation and other contributors | http://jquery.org/license
*/
!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.PointerEventsPolyfill=b()}(this,function(){"use strict";function a(a,b){b=b||Object.create(null);var c=document.createEvent("Event");c.initEvent(a,b.bubbles||!1,b.cancelable||!1);
for(var d,e=2;e<m.length;e++)d=m[e],c[d]=b[d]||n[e];c.buttons=b.buttons||0;
var f=0;return f=b.pressure&&c.buttons?b.pressure:c.buttons?.5:0,c.x=c.clientX,c.y=c.clientY,c.pointerId=b.pointerId||0,c.width=b.width||0,c.height=b.height||0,c.pressure=f,c.tiltX=b.tiltX||0,c.tiltY=b.tiltY||0,c.twist=b.twist||0,c.tangentialPressure=b.tangentialPressure||0,c.pointerType=b.pointerType||"",c.hwTimestamp=b.hwTimestamp||0,c.isPrimary=b.isPrimary||!1,c}function b(){this.array=[],this.size=0}function c(a,b,c,d){this.addCallback=a.bind(d),this.removeCallback=b.bind(d),this.changedCallback=c.bind(d),A&&(this.observer=new A(this.mutationWatcher.bind(this)))}function d(a){return"body /shadow-deep/ "+e(a)}function e(a){return'[touch-action="'+a+'"]'}function f(a){return"{ -ms-touch-action: "+a+"; touch-action: "+a+"; }"}function g(){if(F){D.forEach(function(a){String(a)===a?(E+=e(a)+f(a)+"\n",G&&(E+=d(a)+f(a)+"\n")):(E+=a.selectors.map(e)+f(a.rule)+"\n",G&&(E+=a.selectors.map(d)+f(a.rule)+"\n"))});var a=document.createElement("style");a.textContent=E,document.head.appendChild(a)}}function h(){if(!window.PointerEvent){if(window.PointerEvent=a,window.navigator.msPointerEnabled){var b=window.navigator.msMaxTouchPoints;Object.defineProperty(window.navigator,"maxTouchPoints",{value:b,enumerable:!0}),u.registerSource("ms",_)}else Object.defineProperty(window.navigator,"maxTouchPoints",{value:0,enumerable:!0}),u.registerSource("mouse",N),void 0!==window.ontouchstart&&u.registerSource("touch",V);u.register(document)}}function i(a){if(!u.pointermap.has(a)){var b=new Error("InvalidPointerId");throw b.name="InvalidPointerId",b}}function j(a){for(var b=a.parentNode;b&&b!==a.ownerDocument;)b=b.parentNode;if(!b){var c=new Error("InvalidStateError");throw c.name="InvalidStateError",c}}function k(a){var b=u.pointermap.get(a);return 0!==b.buttons}function l(){window.Element&&!Element.prototype.setPointerCapture&&Object.defineProperties(Element.prototype,{setPointerCapture:{value:W},releasePointerCapture:{value:X},hasPointerCapture:{value:Y}})}
var m=["bubbles","cancelable","view","detail","screenX","screenY","clientX","clientY","ctrlKey","altKey","shiftKey","metaKey","button","relatedTarget","pageX","pageY"],n=[!1,!1,null,null,0,0,0,0,!1,!1,!1,!1,0,null,0,0],o=window.Map&&window.Map.prototype.forEach,p=o?Map:b;b.prototype={set:function(a,b){return void 0===b?this["delete"](a):(this.has(a)||this.size++,void(this.array[a]=b))},has:function(a){return void 0!==this.array[a]},"delete":function(a){this.has(a)&&(delete this.array[a],this.size--)},get:function(a){return this.array[a]},clear:function(){this.array.length=0,this.size=0},forEach:function(a,b){return this.array.forEach(function(c,d){a.call(b,c,d,this)},this)}};var q=["bubbles","cancelable","view","detail","screenX","screenY","clientX","clientY","ctrlKey","altKey","shiftKey","metaKey","button","relatedTarget","buttons","pointerId","width","height","pressure","tiltX","tiltY","pointerType","hwTimestamp","isPrimary","type","target","currentTarget","which","pageX","pageY","timeStamp"],r=[!1,!1,null,null,0,0,0,0,!1,!1,!1,!1,0,null,0,0,0,0,0,0,0,"",0,!1,"",null,null,0,0,0,0],s={pointerover:1,pointerout:1,pointerenter:1,pointerleave:1},t="undefined"!=typeof SVGElementInstance,u={pointermap:new p,eventMap:Object.create(null),captureInfo:Object.create(null),eventSources:Object.create(null),eventSourceList:[],registerSource:function(a,b){var c=b,d=c.events;d&&(d.forEach(function(a){c[a]&&(this.eventMap[a]=c[a].bind(c))},this),this.eventSources[a]=c,this.eventSourceList.push(c))},register:function(a){for(var b,c=this.eventSourceList.length,d=0;d<c&&(b=this.eventSourceList[d]);d++)
b.register.call(b,a)},unregister:function(a){for(var b,c=this.eventSourceList.length,d=0;d<c&&(b=this.eventSourceList[d]);d++)
b.unregister.call(b,a)},contains:function(a,b){try{return a.contains(b)}catch(c){return!1}},down:function(a){a.bubbles=!0,this.fireEvent("pointerdown",a)},move:function(a){a.bubbles=!0,this.fireEvent("pointermove",a)},up:function(a){a.bubbles=!0,this.fireEvent("pointerup",a)},enter:function(a){a.bubbles=!1,this.fireEvent("pointerenter",a)},leave:function(a){a.bubbles=!1,this.fireEvent("pointerleave",a)},over:function(a){a.bubbles=!0,this.fireEvent("pointerover",a)},out:function(a){a.bubbles=!0,this.fireEvent("pointerout",a)},cancel:function(a){a.bubbles=!0,this.fireEvent("pointercancel",a)},leaveOut:function(a){this.out(a),this.propagate(a,this.leave,!1)},enterOver:function(a){this.over(a),this.propagate(a,this.enter,!0)},eventHandler:function(a){if(!a._handledByPE){var b=a.type,c=this.eventMap&&this.eventMap[b];c&&c(a),a._handledByPE=!0}},listen:function(a,b){b.forEach(function(b){this.addEvent(a,b)},this)},unlisten:function(a,b){b.forEach(function(b){this.removeEvent(a,b)},this)},addEvent:function(a,b){a.addEventListener(b,this.boundHandler)},removeEvent:function(a,b){a.removeEventListener(b,this.boundHandler)},makeEvent:function(b,c){this.captureInfo[c.pointerId]&&(c.relatedTarget=null);var d=new a(b,c);return c.preventDefault&&(d.preventDefault=c.preventDefault),d._target=d._target||c.target,d},fireEvent:function(a,b){var c=this.makeEvent(a,b);return this.dispatchEvent(c)},cloneEvent:function(a){for(var b,c=Object.create(null),d=0;d<q.length;d++)b=q[d],c[b]=a[b]||r[d],!t||"target"!==b&&"relatedTarget"!==b||c[b]instanceof SVGElementInstance&&(c[b]=c[b].correspondingUseElement);return a.preventDefault&&(c.preventDefault=function(){a.preventDefault()}),c},getTarget:function(a){var b=this.captureInfo[a.pointerId];return b?a._target!==b&&a.type in s?void 0:b:a._target},propagate:function(a,b,c){for(var d=a.target,e=[];d!==document&&!d.contains(a.relatedTarget);) if(e.push(d),d=d.parentNode,!d)return;c&&e.reverse(),e.forEach(function(c){a.target=c,b.call(this,a)},this)},setCapture:function(b,c,d){this.captureInfo[b]&&this.releaseCapture(b,d),this.captureInfo[b]=c,this.implicitRelease=this.releaseCapture.bind(this,b,d),document.addEventListener("pointerup",this.implicitRelease),document.addEventListener("pointercancel",this.implicitRelease);var e=new a("gotpointercapture");e.pointerId=b,e._target=c,d||this.asyncDispatchEvent(e)},releaseCapture:function(b,c){var d=this.captureInfo[b];if(d){this.captureInfo[b]=void 0,document.removeEventListener("pointerup",this.implicitRelease),document.removeEventListener("pointercancel",this.implicitRelease);var e=new a("lostpointercapture");e.pointerId=b,e._target=d,c||this.asyncDispatchEvent(e)}},dispatchEvent:/*scope.external.dispatchEvent || */function(a){var b=this.getTarget(a);if(b)return b.dispatchEvent(a)},asyncDispatchEvent:function(a){requestAnimationFrame(this.dispatchEvent.bind(this,a))}};u.boundHandler=u.eventHandler.bind(u);var v={shadow:function(a){if(a)return a.shadowRoot||a.webkitShadowRoot},canTarget:function(a){return a&&Boolean(a.elementFromPoint)},targetingShadow:function(a){var b=this.shadow(a);if(this.canTarget(b))return b},olderShadow:function(a){var b=a.olderShadowRoot;if(!b){var c=a.querySelector("shadow");c&&(b=c.olderShadowRoot)}return b},allShadows:function(a){for(var b=[],c=this.shadow(a);c;)b.push(c),c=this.olderShadow(c);return b},searchRoot:function(a,b,c){if(a){var d,e,f=a.elementFromPoint(b,c);for(e=this.targetingShadow(f);e;){if(d=e.elementFromPoint(b,c)){var g=this.targetingShadow(d);return this.searchRoot(g,b,c)||d} e=this.olderShadow(e)} return f}},owner:function(a){
for(var b=a;b.parentNode;)b=b.parentNode;
return b.nodeType!==Node.DOCUMENT_NODE&&b.nodeType!==Node.DOCUMENT_FRAGMENT_NODE&&(b=document),b},findTarget:function(a){var b=a.clientX,c=a.clientY,d=this.owner(a.target);
return d.elementFromPoint(b,c)||(d=document),this.searchRoot(d,b,c)}},w=Array.prototype.forEach.call.bind(Array.prototype.forEach),x=Array.prototype.map.call.bind(Array.prototype.map),y=Array.prototype.slice.call.bind(Array.prototype.slice),z=Array.prototype.filter.call.bind(Array.prototype.filter),A=window.MutationObserver||window.WebKitMutationObserver,B="[touch-action]",C={subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0,attributeFilter:["touch-action"]};c.prototype={watchSubtree:function(a){
//
this.observer&&v.canTarget(a)&&this.observer.observe(a,C)},enableOnSubtree:function(a){this.watchSubtree(a),a===document&&"complete"!==document.readyState?this.installOnLoad():this.installNewSubtree(a)},installNewSubtree:function(a){w(this.findElements(a),this.addElement,this)},findElements:function(a){return a.querySelectorAll?a.querySelectorAll(B):[]},removeElement:function(a){this.removeCallback(a)},addElement:function(a){this.addCallback(a)},elementChanged:function(a,b){this.changedCallback(a,b)},concatLists:function(a,b){return a.concat(y(b))},
installOnLoad:function(){document.addEventListener("readystatechange",function(){"complete"===document.readyState&&this.installNewSubtree(document)}.bind(this))},isElement:function(a){return a.nodeType===Node.ELEMENT_NODE},flattenMutationTree:function(a){
var b=x(a,this.findElements,this);
return b.push(z(a,this.isElement)),b.reduce(this.concatLists,[])},mutationWatcher:function(a){a.forEach(this.mutationHandler,this)},mutationHandler:function(a){if("childList"===a.type){var b=this.flattenMutationTree(a.addedNodes);b.forEach(this.addElement,this);var c=this.flattenMutationTree(a.removedNodes);c.forEach(this.removeElement,this)}else"attributes"===a.type&&this.elementChanged(a.target,a.oldValue)}};var D=["none","auto","pan-x","pan-y",{rule:"pan-x pan-y",selectors:["pan-x pan-y","pan-y pan-x"]}],E="",F=window.PointerEvent||window.MSPointerEvent,G=!window.ShadowDOMPolyfill&&document.head.createShadowRoot,H=u.pointermap,I=25,J=[1,4,2,8,16],K=!1;try{K=1===new MouseEvent("test",{buttons:1}).buttons}catch(L){}
var M,N={POINTER_ID:1,POINTER_TYPE:"mouse",events:["mousedown","mousemove","mouseup","mouseover","mouseout"],register:function(a){u.listen(a,this.events)},unregister:function(a){u.unlisten(a,this.events)},lastTouches:[],
isEventSimulatedFromTouch:function(a){for(var b,c=this.lastTouches,d=a.clientX,e=a.clientY,f=0,g=c.length;f<g&&(b=c[f]);f++){
var h=Math.abs(d-b.x),i=Math.abs(e-b.y);if(h<=I&&i<=I)return!0}},prepareEvent:function(a){var b=u.cloneEvent(a),c=b.preventDefault;return b.preventDefault=function(){a.preventDefault(),c()},b.pointerId=this.POINTER_ID,b.isPrimary=!0,b.pointerType=this.POINTER_TYPE,b},prepareButtonsForMove:function(a,b){var c=H.get(this.POINTER_ID);
0!==b.which&&c?a.buttons=c.buttons:a.buttons=0,b.buttons=a.buttons},mousedown:function(a){if(!this.isEventSimulatedFromTouch(a)){var b=H.get(this.POINTER_ID),c=this.prepareEvent(a);K||(c.buttons=J[c.button],b&&(c.buttons|=b.buttons),a.buttons=c.buttons),H.set(this.POINTER_ID,a),b&&0!==b.buttons?u.move(c):u.down(c)}},mousemove:function(a){if(!this.isEventSimulatedFromTouch(a)){var b=this.prepareEvent(a);K||this.prepareButtonsForMove(b,a),b.button=-1,H.set(this.POINTER_ID,a),u.move(b)}},mouseup:function(a){if(!this.isEventSimulatedFromTouch(a)){var b=H.get(this.POINTER_ID),c=this.prepareEvent(a);if(!K){var d=J[c.button];
c.buttons=b?b.buttons&~d:0,a.buttons=c.buttons}H.set(this.POINTER_ID,a),
c.buttons&=~J[c.button],0===c.buttons?u.up(c):u.move(c)}},mouseover:function(a){if(!this.isEventSimulatedFromTouch(a)){var b=this.prepareEvent(a);K||this.prepareButtonsForMove(b,a),b.button=-1,H.set(this.POINTER_ID,a),u.enterOver(b)}},mouseout:function(a){if(!this.isEventSimulatedFromTouch(a)){var b=this.prepareEvent(a);K||this.prepareButtonsForMove(b,a),b.button=-1,u.leaveOut(b)}},cancel:function(a){var b=this.prepareEvent(a);u.cancel(b),this.deactivateMouse()},deactivateMouse:function(){H["delete"](this.POINTER_ID)}},O=u.captureInfo,P=v.findTarget.bind(v),Q=v.allShadows.bind(v),R=u.pointermap,S=2500,T=200,U="touch-action",V={events:["touchstart","touchmove","touchend","touchcancel"],register:function(a){M.enableOnSubtree(a)},unregister:function(){},elementAdded:function(a){var b=a.getAttribute(U),c=this.touchActionToScrollType(b);c&&(a._scrollType=c,u.listen(a,this.events),
Q(a).forEach(function(a){a._scrollType=c,u.listen(a,this.events)},this))},elementRemoved:function(a){a._scrollType=void 0,u.unlisten(a,this.events),
Q(a).forEach(function(a){a._scrollType=void 0,u.unlisten(a,this.events)},this)},elementChanged:function(a,b){var c=a.getAttribute(U),d=this.touchActionToScrollType(c),e=this.touchActionToScrollType(b);
d&&e?(a._scrollType=d,Q(a).forEach(function(a){a._scrollType=d},this)):e?this.elementRemoved(a):d&&this.elementAdded(a)},scrollTypes:{EMITTER:"none",XSCROLLER:"pan-x",YSCROLLER:"pan-y",SCROLLER:/^(?:pan-x pan-y)|(?:pan-y pan-x)|auto$/},touchActionToScrollType:function(a){var b=a,c=this.scrollTypes;return"none"===b?"none":b===c.XSCROLLER?"X":b===c.YSCROLLER?"Y":c.SCROLLER.exec(b)?"XY":void 0},POINTER_TYPE:"touch",firstTouch:null,isPrimaryTouch:function(a){return this.firstTouch===a.identifier},setPrimaryTouch:function(a){
(0===R.size||1===R.size&&R.has(1))&&(this.firstTouch=a.identifier,this.firstXY={X:a.clientX,Y:a.clientY},this.scrolling=!1,this.cancelResetClickCount())},removePrimaryPointer:function(a){a.isPrimary&&(this.firstTouch=null,this.firstXY=null,this.resetClickCount())},clickCount:0,resetId:null,resetClickCount:function(){var a=function(){this.clickCount=0,this.resetId=null}.bind(this);this.resetId=setTimeout(a,T)},cancelResetClickCount:function(){this.resetId&&clearTimeout(this.resetId)},typeToButtons:function(a){var b=0;return"touchstart"!==a&&"touchmove"!==a||(b=1),b},touchToPointer:function(a){var b=this.currentTouchEvent,c=u.cloneEvent(a),d=c.pointerId=a.identifier+2;c.target=O[d]||P(c),c.bubbles=!0,c.cancelable=!0,c.detail=this.clickCount,c.button=0,c.buttons=this.typeToButtons(b.type),c.width=2*(a.radiusX||a.webkitRadiusX||0),c.height=2*(a.radiusY||a.webkitRadiusY||0),c.pressure=a.force||a.webkitForce||.5,c.isPrimary=this.isPrimaryTouch(a),c.pointerType=this.POINTER_TYPE,
c.altKey=b.altKey,c.ctrlKey=b.ctrlKey,c.metaKey=b.metaKey,c.shiftKey=b.shiftKey;
var e=this;return c.preventDefault=function(){e.scrolling=!1,e.firstXY=null,b.preventDefault()},c},processTouches:function(a,b){var c=a.changedTouches;this.currentTouchEvent=a;for(var d,e=0;e<c.length;e++)d=c[e],b.call(this,this.touchToPointer(d))},
shouldScroll:function(a){if(this.firstXY){var b,c=a.currentTarget._scrollType;if("none"===c)
b=!1;else if("XY"===c)
b=!0;else{var d=a.changedTouches[0],e=c,f="Y"===c?"X":"Y",g=Math.abs(d["client"+e]-this.firstXY[e]),h=Math.abs(d["client"+f]-this.firstXY[f]);
b=g>=h}return this.firstXY=null,b}},findTouch:function(a,b){for(var c,d=0,e=a.length;d<e&&(c=a[d]);d++)if(c.identifier===b)return!0},
vacuumTouches:function(a){var b=a.touches;
if(R.size>=b.length){var c=[];R.forEach(function(a,d){
if(1!==d&&!this.findTouch(b,d-2)){var e=a.out;c.push(e)}},this),c.forEach(this.cancelOut,this)}},touchstart:function(a){this.vacuumTouches(a),this.setPrimaryTouch(a.changedTouches[0]),this.dedupSynthMouse(a),this.scrolling||(this.clickCount++,this.processTouches(a,this.overDown))},overDown:function(a){R.set(a.pointerId,{target:a.target,out:a,outTarget:a.target}),u.enterOver(a),u.down(a)},touchmove:function(a){this.scrolling||(this.shouldScroll(a)?(this.scrolling=!0,this.touchcancel(a)):(a.preventDefault(),this.processTouches(a,this.moveOverOut)))},moveOverOut:function(a){var b=a,c=R.get(b.pointerId);
if(c){var d=c.out,e=c.outTarget;u.move(b),d&&e!==b.target&&(d.relatedTarget=b.target,b.relatedTarget=e,
d.target=e,b.target?(u.leaveOut(d),u.enterOver(b)):(
b.target=e,b.relatedTarget=null,this.cancelOut(b))),c.out=b,c.outTarget=b.target}},touchend:function(a){this.dedupSynthMouse(a),this.processTouches(a,this.upOut)},upOut:function(a){this.scrolling||(u.up(a),u.leaveOut(a)),this.cleanUpPointer(a)},touchcancel:function(a){this.processTouches(a,this.cancelOut)},cancelOut:function(a){u.cancel(a),u.leaveOut(a),this.cleanUpPointer(a)},cleanUpPointer:function(a){R["delete"](a.pointerId),this.removePrimaryPointer(a)},
dedupSynthMouse:function(a){var b=N.lastTouches,c=a.changedTouches[0];
if(this.isPrimaryTouch(c)){
var d={x:c.clientX,y:c.clientY};b.push(d);var e=function(a,b){var c=a.indexOf(b);c>-1&&a.splice(c,1)}.bind(null,b,d);setTimeout(e,S)}}};M=new c(V.elementAdded,V.elementRemoved,V.elementChanged,V);var W,X,Y,Z=u.pointermap,$=window.MSPointerEvent&&"number"==typeof window.MSPointerEvent.MSPOINTER_TYPE_MOUSE,_={events:["MSPointerDown","MSPointerMove","MSPointerUp","MSPointerOut","MSPointerOver","MSPointerCancel","MSGotPointerCapture","MSLostPointerCapture"],register:function(a){u.listen(a,this.events)},unregister:function(a){u.unlisten(a,this.events)},POINTER_TYPES:["","unavailable","touch","pen","mouse"],prepareEvent:function(a){var b=a;return $&&(b=u.cloneEvent(a),b.pointerType=this.POINTER_TYPES[a.pointerType]),b},cleanup:function(a){Z["delete"](a)},MSPointerDown:function(a){Z.set(a.pointerId,a);var b=this.prepareEvent(a);u.down(b)},MSPointerMove:function(a){var b=this.prepareEvent(a);u.move(b)},MSPointerUp:function(a){var b=this.prepareEvent(a);u.up(b),this.cleanup(a.pointerId)},MSPointerOut:function(a){var b=this.prepareEvent(a);u.leaveOut(b)},MSPointerOver:function(a){var b=this.prepareEvent(a);u.enterOver(b)},MSPointerCancel:function(a){var b=this.prepareEvent(a);u.cancel(b),this.cleanup(a.pointerId)},MSLostPointerCapture:function(a){var b=u.makeEvent("lostpointercapture",a);u.dispatchEvent(b)},MSGotPointerCapture:function(a){var b=u.makeEvent("gotpointercapture",a);u.dispatchEvent(b)}},aa=window.navigator;aa.msPointerEnabled?(W=function(a){i(a),j(this),k(a)&&(u.setCapture(a,this,!0),this.msSetPointerCapture(a))},X=function(a){i(a),u.releaseCapture(a,!0),this.msReleasePointerCapture(a)}):(W=function(a){i(a),j(this),k(a)&&u.setCapture(a,this)},X=function(a){i(a),u.releaseCapture(a)}),Y=function(a){return!!u.captureInfo[a]},g(),h(),l();var ba={dispatcher:u,Installer:c,PointerEvent:a,PointerMap:p,targetFinding:v};return ba});

View File

@ -0,0 +1,6 @@
/*
Split.js - v1.3.5
MIT License
https://github.com/nathancahill/Split.js
*/
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Split=t()}(this,function(){"use strict";var e=window,t=e.document,n="addEventListener",i="removeEventListener",r="getBoundingClientRect",s=function(){return!1},o=e.attachEvent&&!e[n],a=["","-webkit-","-moz-","-o-"].filter(function(e){var n=t.createElement("div");return n.style.cssText="width:"+e+"calc(9px)",!!n.style.length}).shift()+"calc",l=function(e){return"string"==typeof e||e instanceof String?t.querySelector(e):e};return function(u,c){function z(e,t,n){var i=A(y,t,n);Object.keys(i).forEach(function(t){return e.style[t]=i[t]})}function h(e,t){var n=B(y,t);Object.keys(n).forEach(function(t){return e.style[t]=n[t]})}function f(e){var t=E[this.a],n=E[this.b],i=t.size+n.size;t.size=e/this.size*i,n.size=i-e/this.size*i,z(t.element,t.size,this.aGutterSize),z(n.element,n.size,this.bGutterSize)}function m(e){var t;this.dragging&&((t="touches"in e?e.touches[0][b]-this.start:e[b]-this.start)<=E[this.a].minSize+M+this.aGutterSize?t=E[this.a].minSize+this.aGutterSize:t>=this.size-(E[this.b].minSize+M+this.bGutterSize)&&(t=this.size-(E[this.b].minSize+this.bGutterSize)),f.call(this,t),c.onDrag&&c.onDrag())}function g(){var e=E[this.a].element,t=E[this.b].element;this.size=e[r]()[y]+t[r]()[y]+this.aGutterSize+this.bGutterSize,this.start=e[r]()[G]}function d(){var t=this,n=E[t.a].element,r=E[t.b].element;t.dragging&&c.onDragEnd&&c.onDragEnd(),t.dragging=!1,e[i]("mouseup",t.stop),e[i]("touchend",t.stop),e[i]("touchcancel",t.stop),t.parent[i]("mousemove",t.move),t.parent[i]("touchmove",t.move),delete t.stop,delete t.move,n[i]("selectstart",s),n[i]("dragstart",s),r[i]("selectstart",s),r[i]("dragstart",s),n.style.userSelect="",n.style.webkitUserSelect="",n.style.MozUserSelect="",n.style.pointerEvents="",r.style.userSelect="",r.style.webkitUserSelect="",r.style.MozUserSelect="",r.style.pointerEvents="",t.gutter.style.cursor="",t.parent.style.cursor=""}function S(t){var i=this,r=E[i.a].element,o=E[i.b].element;!i.dragging&&c.onDragStart&&c.onDragStart(),t.preventDefault(),i.dragging=!0,i.move=m.bind(i),i.stop=d.bind(i),e[n]("mouseup",i.stop),e[n]("touchend",i.stop),e[n]("touchcancel",i.stop),i.parent[n]("mousemove",i.move),i.parent[n]("touchmove",i.move),r[n]("selectstart",s),r[n]("dragstart",s),o[n]("selectstart",s),o[n]("dragstart",s),r.style.userSelect="none",r.style.webkitUserSelect="none",r.style.MozUserSelect="none",r.style.pointerEvents="none",o.style.userSelect="none",o.style.webkitUserSelect="none",o.style.MozUserSelect="none",o.style.pointerEvents="none",i.gutter.style.cursor=j,i.parent.style.cursor=j,g.call(i)}function v(e){e.forEach(function(t,n){if(n>0){var i=F[n-1],r=E[i.a],s=E[i.b];r.size=e[n-1],s.size=t,z(r.element,r.size,i.aGutterSize),z(s.element,s.size,i.bGutterSize)}})}function p(){F.forEach(function(e){e.parent.removeChild(e.gutter),E[e.a].element.style[y]="",E[e.b].element.style[y]=""})}void 0===c&&(c={});var y,b,G,E,w=l(u[0]).parentNode,D=e.getComputedStyle(w).flexDirection,U=c.sizes||u.map(function(){return 100/u.length}),k=void 0!==c.minSize?c.minSize:100,x=Array.isArray(k)?k:u.map(function(){return k}),L=void 0!==c.gutterSize?c.gutterSize:10,M=void 0!==c.snapOffset?c.snapOffset:30,O=c.direction||"horizontal",j=c.cursor||("horizontal"===O?"ew-resize":"ns-resize"),C=c.gutter||function(e,n){var i=t.createElement("div");return i.className="gutter gutter-"+n,i},A=c.elementStyle||function(e,t,n){var i={};return"string"==typeof t||t instanceof String?i[e]=t:i[e]=o?t+"%":a+"("+t+"% - "+n+"px)",i},B=c.gutterStyle||function(e,t){return n={},n[e]=t+"px",n;var n};"horizontal"===O?(y="width","clientWidth",b="clientX",G="left","paddingLeft"):"vertical"===O&&(y="height","clientHeight",b="clientY",G="top","paddingTop");var F=[];return E=u.map(function(e,t){var i,s={element:l(e),size:U[t],minSize:x[t]};if(t>0&&(i={a:t-1,b:t,dragging:!1,isFirst:1===t,isLast:t===u.length-1,direction:O,parent:w},i.aGutterSize=L,i.bGutterSize=L,i.isFirst&&(i.aGutterSize=L/2),i.isLast&&(i.bGutterSize=L/2),"row-reverse"===D||"column-reverse"===D)){var a=i.a;i.a=i.b,i.b=a}if(!o&&t>0){var c=C(t,O);h(c,L),c[n]("mousedown",S.bind(i)),c[n]("touchstart",S.bind(i)),w.insertBefore(c,s.element),i.gutter=c}0===t||t===u.length-1?z(s.element,s.size,L/2):z(s.element,s.size,L);var f=s.element[r]()[y];return f<s.minSize&&(s.minSize=f),t>0&&F.push(i),s}),o?{setSizes:v,destroy:p}:{setSizes:v,getSizes:function(){return E.map(function(e){return e.size})},collapse:function(e){if(e===F.length){var t=F[e-1];g.call(t),o||f.call(t,t.size-t.bGutterSize)}else{var n=F[e];g.call(n),o||f.call(n,n.aGutterSize)}},destroy:p}}});

View File

@ -0,0 +1,371 @@
/*
* Table reordering via Drag'n'Drop
* Inspired by: https://htmldom.dev/drag-and-drop-table-column
*/
function setBomHandlers() {
const bom = document.getElementById('bomtable');
let dragName;
let placeHolderElements;
let draggingElement;
let forcePopulation;
let xOffset;
let yOffset;
let wasDragged;
const mouseUpHandler = function(e) {
// Delete dragging element
draggingElement.remove();
// Make BOM selectable again
bom.style.removeProperty("userSelect");
// Remove listeners
document.removeEventListener('mousemove', mouseMoveHandler);
document.removeEventListener('mouseup', mouseUpHandler);
if (wasDragged) {
// Redraw whole BOM
populateBomTable();
}
}
const mouseMoveHandler = function(e) {
// Notice the dragging
wasDragged = true;
// Make the dragged element visible
draggingElement.style.removeProperty("display");
// Set elements position to mouse position
draggingElement.style.left = `${e.screenX - xOffset}px`;
draggingElement.style.top = `${e.screenY - yOffset}px`;
// Forced redrawing of BOM table
if (forcePopulation) {
forcePopulation = false;
// Copy array
phe = Array.from(placeHolderElements);
// populate BOM table again
populateBomHeader(dragName, phe);
populateBomBody(dragName, phe);
}
// Set up array of hidden columns
var hiddenColumns = Array.from(settings.hiddenColumns);
// In the ungrouped mode, quantity don't exist
if (settings.bommode === "ungrouped")
hiddenColumns.push("Quantity");
// If no checkbox fields can be found, we consider them hidden
if (settings.checkboxes.length == 0)
hiddenColumns.push("checkboxes");
// Get table headers and group them into checkboxes, extrafields and normal headers
const bh = document.getElementById("bomhead");
headers = Array.from(bh.querySelectorAll("th"))
headers.shift() // numCol is not part of the columnOrder
headerGroups = []
lastCompoundClass = null;
for (i = 0; i < settings.columnOrder.length; i++) {
cElem = settings.columnOrder[i];
if (hiddenColumns.includes(cElem)) {
// Hidden columns appear as a dummy element
headerGroups.push([]);
continue;
}
elem = headers.filter(e => getColumnOrderName(e) === cElem)[0];
if (elem.classList.contains("bom-checkbox")) {
if (lastCompoundClass === "bom-checkbox") {
cbGroup = headerGroups.pop();
cbGroup.push(elem);
headerGroups.push(cbGroup);
} else {
lastCompoundClass = "bom-checkbox";
headerGroups.push([elem])
}
} else {
headerGroups.push([elem])
}
}
// Copy settings.columnOrder
var columns = Array.from(settings.columnOrder)
// Set up array with indices of hidden columns
var hiddenIndices = hiddenColumns.map(e => settings.columnOrder.indexOf(e));
var dragIndex = columns.indexOf(dragName);
var swapIndex = dragIndex;
var swapDone = false;
// Check if the current dragged element is swapable with the left or right element
if (dragIndex > 0) {
// Get left headers boundingbox
swapIndex = dragIndex - 1;
while (hiddenIndices.includes(swapIndex) && swapIndex > 0)
swapIndex--;
if (!hiddenIndices.includes(swapIndex)) {
box = getBoundingClientRectFromMultiple(headerGroups[swapIndex]);
if (e.clientX < box.left + window.scrollX + (box.width / 2)) {
swapElement = columns[dragIndex];
columns.splice(dragIndex, 1);
columns.splice(swapIndex, 0, swapElement);
forcePopulation = true;
swapDone = true;
}
}
}
if ((!swapDone) && dragIndex < headerGroups.length - 1) {
// Get right headers boundingbox
swapIndex = dragIndex + 1;
while (hiddenIndices.includes(swapIndex))
swapIndex++;
if (swapIndex < headerGroups.length) {
box = getBoundingClientRectFromMultiple(headerGroups[swapIndex]);
if (e.clientX > box.left + window.scrollX + (box.width / 2)) {
swapElement = columns[dragIndex];
columns.splice(dragIndex, 1);
columns.splice(swapIndex, 0, swapElement);
forcePopulation = true;
swapDone = true;
}
}
}
// Write back change to storage
if (swapDone) {
settings.columnOrder = columns
writeStorage("columnOrder", JSON.stringify(columns));
}
}
const mouseDownHandler = function(e) {
var target = e.target;
if (target.tagName.toLowerCase() != "td")
target = target.parentElement;
// Used to check if a dragging has ever happened
wasDragged = false;
// Create new element which will be displayed as the dragged column
draggingElement = document.createElement("div")
draggingElement.classList.add("dragging");
draggingElement.style.display = "none";
draggingElement.style.position = "absolute";
draggingElement.style.overflow = "hidden";
// Get bomhead and bombody elements
const bh = document.getElementById("bomhead");
const bb = document.getElementById("bombody");
// Get all compound headers for the current column
var compoundHeaders;
if (target.classList.contains("bom-checkbox")) {
compoundHeaders = Array.from(bh.querySelectorAll("th.bom-checkbox"));
} else {
compoundHeaders = [target];
}
// Create new table which will display the column
var newTable = document.createElement("table");
newTable.classList.add("bom");
newTable.style.background = "white";
draggingElement.append(newTable);
// Create new header element
var newHeader = document.createElement("thead");
newTable.append(newHeader);
// Set up array for storing all placeholder elements
placeHolderElements = [];
// Add all compound headers to the new thead element and placeholders
compoundHeaders.forEach(function(h) {
clone = cloneElementWithDimensions(h);
newHeader.append(clone);
placeHolderElements.push(clone);
});
// Create new body element
var newBody = document.createElement("tbody");
newTable.append(newBody);
// Get indices for compound headers
var idxs = compoundHeaders.map(e => getBomTableHeaderIndex(e));
// For each row in the BOM body...
var rows = bb.querySelectorAll("tr");
rows.forEach(function(row) {
// ..get the cells for the compound column
const tds = row.querySelectorAll("td");
var copytds = idxs.map(i => tds[i]);
// Add them to the new element and the placeholders
var newRow = document.createElement("tr");
copytds.forEach(function(td) {
clone = cloneElementWithDimensions(td);
newRow.append(clone);
placeHolderElements.push(clone);
});
newBody.append(newRow);
});
// Compute width for compound header
var width = compoundHeaders.reduce((acc, x) => acc + x.clientWidth, 0);
draggingElement.style.width = `${width}px`;
// Insert the new dragging element and disable selection on BOM
bom.insertBefore(draggingElement, null);
bom.style.userSelect = "none";
// Determine the mouse position offset
xOffset = e.screenX - compoundHeaders.reduce((acc, x) => Math.min(acc, x.offsetLeft), compoundHeaders[0].offsetLeft);
yOffset = e.screenY - compoundHeaders[0].offsetTop;
// Get name for the column in settings.columnOrder
dragName = getColumnOrderName(target);
// Change text and class for placeholder elements
placeHolderElements = placeHolderElements.map(function(e) {
newElem = cloneElementWithDimensions(e);
newElem.textContent = "";
newElem.classList.add("placeholder");
return newElem;
});
// On next mouse move, the whole BOM needs to be redrawn to show the placeholders
forcePopulation = true;
// Add listeners for move and up on mouse
document.addEventListener('mousemove', mouseMoveHandler);
document.addEventListener('mouseup', mouseUpHandler);
}
// In netlist mode, there is nothing to reorder
if (settings.bommode === "netlist")
return;
// Add mouseDownHandler to every column except the numCol
bom.querySelectorAll("th")
.forEach(function(head) {
if (!head.classList.contains("numCol")) {
head.onmousedown = mouseDownHandler;
}
});
}
function getBoundingClientRectFromMultiple(elements) {
var elems = Array.from(elements);
if (elems.length == 0)
return null;
var box = elems.shift()
.getBoundingClientRect();
elems.forEach(function(elem) {
var elembox = elem.getBoundingClientRect();
box.left = Math.min(elembox.left, box.left);
box.top = Math.min(elembox.top, box.top);
box.width += elembox.width;
box.height = Math.max(elembox.height, box.height);
});
return box;
}
function cloneElementWithDimensions(elem) {
var newElem = elem.cloneNode(true);
newElem.style.height = window.getComputedStyle(elem).height;
newElem.style.width = window.getComputedStyle(elem).width;
return newElem;
}
function getBomTableHeaderIndex(elem) {
const bh = document.getElementById('bomhead');
const ths = Array.from(bh.querySelectorAll("th"));
return ths.indexOf(elem);
}
function getColumnOrderName(elem) {
var cname = elem.getAttribute("col_name");
if (cname === "bom-checkbox")
return "checkboxes";
else
return cname;
}
function resizableGrid(tablehead) {
var cols = tablehead.firstElementChild.children;
var rowWidth = tablehead.offsetWidth;
for (var i = 1; i < cols.length; i++) {
if (cols[i].classList.contains("bom-checkbox"))
continue;
cols[i].style.width = ((cols[i].clientWidth - paddingDiff(cols[i])) * 100 / rowWidth) + '%';
}
for (var i = 1; i < cols.length - 1; i++) {
var div = document.createElement('div');
div.className = "column-width-handle";
cols[i].appendChild(div);
setListeners(div);
}
function setListeners(div) {
var startX, curCol, nxtCol, curColWidth, nxtColWidth, rowWidth;
div.addEventListener('mousedown', function(e) {
e.preventDefault();
e.stopPropagation();
curCol = e.target.parentElement;
nxtCol = curCol.nextElementSibling;
startX = e.pageX;
var padding = paddingDiff(curCol);
rowWidth = curCol.parentElement.offsetWidth;
curColWidth = curCol.clientWidth - padding;
nxtColWidth = nxtCol.clientWidth - padding;
});
document.addEventListener('mousemove', function(e) {
if (startX) {
var diffX = e.pageX - startX;
diffX = -Math.min(-diffX, curColWidth - 20);
diffX = Math.min(diffX, nxtColWidth - 20);
curCol.style.width = ((curColWidth + diffX) * 100 / rowWidth) + '%';
nxtCol.style.width = ((nxtColWidth - diffX) * 100 / rowWidth) + '%';
console.log(`${curColWidth + nxtColWidth} ${(curColWidth + diffX) * 100 / rowWidth + (nxtColWidth - diffX) * 100 / rowWidth}`);
}
});
document.addEventListener('mouseup', function(e) {
curCol = undefined;
nxtCol = undefined;
startX = undefined;
nxtColWidth = undefined;
curColWidth = undefined
});
}
function paddingDiff(col) {
if (getStyleVal(col, 'box-sizing') == 'border-box') {
return 0;
}
var padLeft = getStyleVal(col, 'padding-left');
var padRight = getStyleVal(col, 'padding-right');
return (parseInt(padLeft) + parseInt(padRight));
}
function getStyleVal(elm, css) {
return (window.getComputedStyle(elm, null).getPropertyValue(css))
}
}

View File

@ -0,0 +1 @@
/* Add custom css styles and overrides here. */

View File

@ -0,0 +1,4 @@
// Example event listener.
// Event argument will always be {eventType, args} dict
EventHandler.registerCallback(IBOM_EVENT_TYPES.ALL, (e) => console.log(e));

View File

@ -0,0 +1,5 @@
<!-- example footer -->
</div>
<div style="width:100%; height: 50px;">
Hello footer
</div>

View File

@ -0,0 +1,5 @@
<!-- example header -->
<div style="width:100%; height: 50px;">
Hello header
</div>
<div style="height: calc(100% - 100px)">

View File

@ -0,0 +1,610 @@
/* Utility functions */
var storagePrefix = 'KiCad_HTML_BOM__' + pcbdata.metadata.title + '__' +
pcbdata.metadata.revision + '__#';
var storage;
function initStorage(key) {
try {
window.localStorage.getItem("blank");
storage = window.localStorage;
} catch (e) {
// localStorage not available
}
if (!storage) {
try {
window.sessionStorage.getItem("blank");
storage = window.sessionStorage;
} catch (e) {
// sessionStorage also not available
}
}
}
function readStorage(key) {
if (storage) {
return storage.getItem(storagePrefix + key);
} else {
return null;
}
}
function writeStorage(key, value) {
if (storage) {
storage.setItem(storagePrefix + key, value);
}
}
function fancyDblClickHandler(el, onsingle, ondouble) {
return function() {
if (el.getAttribute("data-dblclick") == null) {
el.setAttribute("data-dblclick", 1);
setTimeout(function() {
if (el.getAttribute("data-dblclick") == 1) {
onsingle();
}
el.removeAttribute("data-dblclick");
}, 200);
} else {
el.removeAttribute("data-dblclick");
ondouble();
}
}
}
function smoothScrollToRow(rowid) {
document.getElementById(rowid).scrollIntoView({
behavior: "smooth",
block: "center",
inline: "nearest"
});
}
function focusInputField(input) {
input.scrollIntoView(false);
input.focus();
input.select();
}
function saveBomTable(output) {
var text = '';
for (var node of bomhead.childNodes[0].childNodes) {
if (node.firstChild) {
text += (output == 'csv' ? `"${node.firstChild.nodeValue}"` : node.firstChild.nodeValue);
}
if (node != bomhead.childNodes[0].lastChild) {
text += (output == 'csv' ? ',' : '\t');
}
}
text += '\n';
for (var row of bombody.childNodes) {
for (var cell of row.childNodes) {
let val = '';
for (var node of cell.childNodes) {
if (node.nodeName == "INPUT") {
if (node.checked) {
val += '✓';
}
} else if ((node.nodeName == "MARK") || (node.nodeName == "A")) {
val += node.firstChild.nodeValue;
} else {
val += node.nodeValue;
}
}
if (output == 'csv') {
val = val.replace(/\"/g, '\"\"'); // pair of double-quote characters
if (isNumeric(val)) {
val = +val; // use number
} else {
val = `"${val}"`; // enclosed within double-quote
}
}
text += val;
if (cell != row.lastChild) {
text += (output == 'csv' ? ',' : '\t');
}
}
text += '\n';
}
if (output != 'clipboard') {
// To file: csv or txt
var blob = new Blob([text], {
type: `text/${output}`
});
saveFile(`${pcbdata.metadata.title}.${output}`, blob);
} else {
// To clipboard
var textArea = document.createElement("textarea");
textArea.classList.add('clipboard-temp');
textArea.value = text;
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
if (document.execCommand('copy')) {
console.log('Bom copied to clipboard.');
}
} catch (err) {
console.log('Can not copy to clipboard.');
}
document.body.removeChild(textArea);
}
}
function isNumeric(str) {
/* https://stackoverflow.com/a/175787 */
return (typeof str != "string" ? false : !isNaN(str) && !isNaN(parseFloat(str)));
}
function removeGutterNode(node) {
for (var i = 0; i < node.childNodes.length; i++) {
if (node.childNodes[i].classList &&
node.childNodes[i].classList.contains("gutter")) {
node.removeChild(node.childNodes[i]);
break;
}
}
}
function cleanGutters() {
removeGutterNode(document.getElementById("bot"));
removeGutterNode(document.getElementById("canvasdiv"));
}
var units = {
prefixes: {
giga: ["G", "g", "giga", "Giga", "GIGA"],
mega: ["M", "mega", "Mega", "MEGA"],
kilo: ["K", "k", "kilo", "Kilo", "KILO"],
milli: ["m", "milli", "Milli", "MILLI"],
micro: ["U", "u", "micro", "Micro", "MICRO", "μ", "µ"], // different utf8 μ
nano: ["N", "n", "nano", "Nano", "NANO"],
pico: ["P", "p", "pico", "Pico", "PICO"],
},
unitsShort: ["R", "r", "Ω", "F", "f", "H", "h"],
unitsLong: [
"OHM", "Ohm", "ohm", "ohms",
"FARAD", "Farad", "farad",
"HENRY", "Henry", "henry"
],
getMultiplier: function(s) {
if (this.prefixes.giga.includes(s)) return 1e9;
if (this.prefixes.mega.includes(s)) return 1e6;
if (this.prefixes.kilo.includes(s)) return 1e3;
if (this.prefixes.milli.includes(s)) return 1e-3;
if (this.prefixes.micro.includes(s)) return 1e-6;
if (this.prefixes.nano.includes(s)) return 1e-9;
if (this.prefixes.pico.includes(s)) return 1e-12;
return 1;
},
valueRegex: null,
}
function initUtils() {
var allPrefixes = units.prefixes.giga
.concat(units.prefixes.mega)
.concat(units.prefixes.kilo)
.concat(units.prefixes.milli)
.concat(units.prefixes.micro)
.concat(units.prefixes.nano)
.concat(units.prefixes.pico);
var allUnits = units.unitsShort.concat(units.unitsLong);
units.valueRegex = new RegExp("^([0-9\.]+)" +
"\\s*(" + allPrefixes.join("|") + ")?" +
"(" + allUnits.join("|") + ")?" +
"(\\b.*)?$", "");
units.valueAltRegex = new RegExp("^([0-9]*)" +
"(" + units.unitsShort.join("|") + ")?" +
"([GgMmKkUuNnPp])?" +
"([0-9]*)" +
"(\\b.*)?$", "");
if (config.fields.includes("Value")) {
var index = config.fields.indexOf("Value");
pcbdata.bom["parsedValues"] = {};
for (var id in pcbdata.bom.fields) {
pcbdata.bom.parsedValues[id] = parseValue(pcbdata.bom.fields[id][index])
}
}
}
function parseValue(val, ref) {
var inferUnit = (unit, ref) => {
if (unit) {
unit = unit.toLowerCase();
if (unit == 'Ω' || unit == "ohm" || unit == "ohms") {
unit = 'r';
}
unit = unit[0];
} else {
ref = /^([a-z]+)\d+$/i.exec(ref);
if (ref) {
ref = ref[1].toLowerCase();
if (ref == "c") unit = 'f';
else if (ref == "l") unit = 'h';
else if (ref == "r" || ref == "rv") unit = 'r';
else unit = null;
}
}
return unit;
};
val = val.replace(/,/g, "");
var match = units.valueRegex.exec(val);
var unit;
if (match) {
val = parseFloat(match[1]);
if (match[2]) {
val = val * units.getMultiplier(match[2]);
}
unit = inferUnit(match[3], ref);
if (!unit) return null;
else return {
val: val,
unit: unit,
extra: match[4],
}
}
match = units.valueAltRegex.exec(val);
if (match && (match[1] || match[4])) {
val = parseFloat(match[1] + "." + match[4]);
if (match[3]) {
val = val * units.getMultiplier(match[3]);
}
unit = inferUnit(match[2], ref);
if (!unit) return null;
else return {
val: val,
unit: unit,
extra: match[5],
}
}
return null;
}
function valueCompare(a, b, stra, strb) {
if (a === null && b === null) {
// Failed to parse both values, compare them as strings.
if (stra != strb) return stra > strb ? 1 : -1;
else return 0;
} else if (a === null) {
return 1;
} else if (b === null) {
return -1;
} else {
if (a.unit != b.unit) return a.unit > b.unit ? 1 : -1;
else if (a.val != b.val) return a.val > b.val ? 1 : -1;
else if (a.extra != b.extra) return a.extra > b.extra ? 1 : -1;
else return 0;
}
}
function validateSaveImgDimension(element) {
var valid = false;
var intValue = 0;
if (/^[1-9]\d*$/.test(element.value)) {
intValue = parseInt(element.value);
if (intValue <= 16000) {
valid = true;
}
}
if (valid) {
element.classList.remove("invalid");
} else {
element.classList.add("invalid");
}
return intValue;
}
function saveImage(layer) {
var width = validateSaveImgDimension(document.getElementById("render-save-width"));
var height = validateSaveImgDimension(document.getElementById("render-save-height"));
var bgcolor = null;
if (!document.getElementById("render-save-transparent").checked) {
var style = getComputedStyle(topmostdiv);
bgcolor = style.getPropertyValue("background-color");
}
if (!width || !height) return;
// Prepare image
var canvas = document.createElement("canvas");
var layerdict = {
transform: {
x: 0,
y: 0,
s: 1,
panx: 0,
pany: 0,
zoom: 1,
},
bg: canvas,
fab: canvas,
silk: canvas,
highlight: canvas,
layer: layer,
}
// Do the rendering
recalcLayerScale(layerdict, width, height);
prepareLayer(layerdict);
clearCanvas(canvas, bgcolor);
drawBackground(layerdict, false);
drawHighlightsOnLayer(layerdict, false);
// Save image
var imgdata = canvas.toDataURL("image/png");
var filename = pcbdata.metadata.title;
if (pcbdata.metadata.revision) {
filename += `.${pcbdata.metadata.revision}`;
}
filename += `.${layer}.png`;
saveFile(filename, dataURLtoBlob(imgdata));
}
function saveSettings() {
var data = {
type: "InteractiveHtmlBom settings",
version: 1,
pcbmetadata: pcbdata.metadata,
settings: settings,
}
var blob = new Blob([JSON.stringify(data, null, 4)], {
type: "application/json"
});
saveFile(`${pcbdata.metadata.title}.settings.json`, blob);
}
function loadSettings() {
var input = document.createElement("input");
input.type = "file";
input.accept = ".settings.json";
input.onchange = function(e) {
var file = e.target.files[0];
var reader = new FileReader();
reader.onload = readerEvent => {
var content = readerEvent.target.result;
var newSettings;
try {
newSettings = JSON.parse(content);
} catch (e) {
alert("Selected file is not InteractiveHtmlBom settings file.");
return;
}
if (newSettings.type != "InteractiveHtmlBom settings") {
alert("Selected file is not InteractiveHtmlBom settings file.");
return;
}
var metadataMatches = newSettings.hasOwnProperty("pcbmetadata");
if (metadataMatches) {
for (var k in pcbdata.metadata) {
if (!newSettings.pcbmetadata.hasOwnProperty(k) || newSettings.pcbmetadata[k] != pcbdata.metadata[k]) {
metadataMatches = false;
}
}
}
if (!metadataMatches) {
var currentMetadata = JSON.stringify(pcbdata.metadata, null, 4);
var fileMetadata = JSON.stringify(newSettings.pcbmetadata, null, 4);
if (!confirm(
`Settins file metadata does not match current metadata.\n\n` +
`Page metadata:\n${currentMetadata}\n\n` +
`Settings file metadata:\n${fileMetadata}\n\n` +
`Press OK if you would like to import settings anyway.`)) {
return;
}
}
overwriteSettings(newSettings.settings);
}
reader.readAsText(file, 'UTF-8');
}
input.click();
}
function overwriteSettings(newSettings) {
initDone = false;
Object.assign(settings, newSettings);
writeStorage("bomlayout", settings.bomlayout);
writeStorage("bommode", settings.bommode);
writeStorage("canvaslayout", settings.canvaslayout);
writeStorage("bomCheckboxes", settings.checkboxes.join(","));
document.getElementById("bomCheckboxes").value = settings.checkboxes.join(",");
for (var checkbox of settings.checkboxes) {
writeStorage("checkbox_" + checkbox, settings.checkboxStoredRefs[checkbox]);
}
writeStorage("markWhenChecked", settings.markWhenChecked);
padsVisible(settings.renderPads);
document.getElementById("padsCheckbox").checked = settings.renderPads;
fabricationVisible(settings.renderFabrication);
document.getElementById("fabricationCheckbox").checked = settings.renderFabrication;
silkscreenVisible(settings.renderSilkscreen);
document.getElementById("silkscreenCheckbox").checked = settings.renderSilkscreen;
referencesVisible(settings.renderReferences);
document.getElementById("referencesCheckbox").checked = settings.renderReferences;
valuesVisible(settings.renderValues);
document.getElementById("valuesCheckbox").checked = settings.renderValues;
tracksVisible(settings.renderTracks);
document.getElementById("tracksCheckbox").checked = settings.renderTracks;
zonesVisible(settings.renderZones);
document.getElementById("zonesCheckbox").checked = settings.renderZones;
dnpOutline(settings.renderDnpOutline);
document.getElementById("dnpOutlineCheckbox").checked = settings.renderDnpOutline;
setRedrawOnDrag(settings.redrawOnDrag);
document.getElementById("dragCheckbox").checked = settings.redrawOnDrag;
setDarkMode(settings.darkMode);
document.getElementById("darkmodeCheckbox").checked = settings.darkMode;
setHighlightPin1(settings.highlightpin1);
document.getElementById("highlightpin1Checkbox").checked = settings.highlightpin1;
writeStorage("boardRotation", settings.boardRotation);
document.getElementById("boardRotation").value = settings.boardRotation / 5;
document.getElementById("rotationDegree").textContent = settings.boardRotation;
setOffsetBackRotation(settings.offsetBackRotation);
document.getElementById("offsetBackRotationCheckbox").checked = settings.offsetBackRotation;
initDone = true;
prepCheckboxes();
changeBomLayout(settings.bomlayout);
}
function saveFile(filename, blob) {
var link = document.createElement("a");
var objurl = URL.createObjectURL(blob);
link.download = filename;
link.href = objurl;
link.click();
}
function dataURLtoBlob(dataurl) {
var arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], {
type: mime
});
}
var settings = {
canvaslayout: "default",
bomlayout: "default",
bommode: "grouped",
checkboxes: [],
checkboxStoredRefs: {},
darkMode: false,
highlightpin1: false,
redrawOnDrag: true,
boardRotation: 0,
offsetBackRotation: false,
renderPads: true,
renderReferences: true,
renderValues: true,
renderSilkscreen: true,
renderFabrication: true,
renderDnpOutline: false,
renderTracks: true,
renderZones: true,
columnOrder: [],
hiddenColumns: []
}
function initDefaults() {
settings.bomlayout = readStorage("bomlayout");
if (settings.bomlayout === null) {
settings.bomlayout = config.bom_view;
}
if (!['bom-only', 'left-right', 'top-bottom'].includes(settings.bomlayout)) {
settings.bomlayout = config.bom_view;
}
settings.bommode = readStorage("bommode");
if (settings.bommode === null) {
settings.bommode = "grouped";
}
if (!["grouped", "ungrouped", "netlist"].includes(settings.bommode)) {
settings.bommode = "grouped";
}
settings.canvaslayout = readStorage("canvaslayout");
if (settings.canvaslayout === null) {
settings.canvaslayout = config.layer_view;
}
var bomCheckboxes = readStorage("bomCheckboxes");
if (bomCheckboxes === null) {
bomCheckboxes = config.checkboxes;
}
settings.checkboxes = bomCheckboxes.split(",").filter((e) => e);
document.getElementById("bomCheckboxes").value = bomCheckboxes;
settings.markWhenChecked = readStorage("markWhenChecked") || "";
populateMarkWhenCheckedOptions();
function initBooleanSetting(storageString, def, elementId, func) {
var b = readStorage(storageString);
if (b === null) {
b = def;
} else {
b = (b == "true");
}
document.getElementById(elementId).checked = b;
func(b);
}
initBooleanSetting("padsVisible", config.show_pads, "padsCheckbox", padsVisible);
initBooleanSetting("fabricationVisible", config.show_fabrication, "fabricationCheckbox", fabricationVisible);
initBooleanSetting("silkscreenVisible", config.show_silkscreen, "silkscreenCheckbox", silkscreenVisible);
initBooleanSetting("referencesVisible", true, "referencesCheckbox", referencesVisible);
initBooleanSetting("valuesVisible", true, "valuesCheckbox", valuesVisible);
if ("tracks" in pcbdata) {
initBooleanSetting("tracksVisible", true, "tracksCheckbox", tracksVisible);
initBooleanSetting("zonesVisible", true, "zonesCheckbox", zonesVisible);
} else {
document.getElementById("tracksAndZonesCheckboxes").style.display = "none";
tracksVisible(false);
zonesVisible(false);
}
initBooleanSetting("dnpOutline", false, "dnpOutlineCheckbox", dnpOutline);
initBooleanSetting("redrawOnDrag", config.redraw_on_drag, "dragCheckbox", setRedrawOnDrag);
initBooleanSetting("darkmode", config.dark_mode, "darkmodeCheckbox", setDarkMode);
initBooleanSetting("highlightpin1", config.highlight_pin1, "highlightpin1Checkbox", setHighlightPin1);
var fields = ["checkboxes", "References"].concat(config.fields).concat(["Quantity"]);
var hcols = JSON.parse(readStorage("hiddenColumns"));
if (hcols === null) {
hcols = [];
}
settings.hiddenColumns = hcols.filter(e => fields.includes(e));
var cord = JSON.parse(readStorage("columnOrder"));
if (cord === null) {
cord = fields;
} else {
cord = cord.filter(e => fields.includes(e));
if (cord.length != fields.length)
cord = fields;
}
settings.columnOrder = cord;
settings.boardRotation = readStorage("boardRotation");
if (settings.boardRotation === null) {
settings.boardRotation = config.board_rotation * 5;
} else {
settings.boardRotation = parseInt(settings.boardRotation);
}
document.getElementById("boardRotation").value = settings.boardRotation / 5;
document.getElementById("rotationDegree").textContent = settings.boardRotation;
initBooleanSetting("offsetBackRotation", config.offset_back_rotation, "offsetBackRotationCheckbox", setOffsetBackRotation);
}
// Helper classes for user js callbacks.
const IBOM_EVENT_TYPES = {
ALL: "all",
HIGHLIGHT_EVENT: "highlightEvent",
CHECKBOX_CHANGE_EVENT: "checkboxChangeEvent",
BOM_BODY_CHANGE_EVENT: "bomBodyChangeEvent",
}
const EventHandler = {
callbacks: {},
init: function() {
for (eventType of Object.values(IBOM_EVENT_TYPES))
this.callbacks[eventType] = [];
},
registerCallback: function(eventType, callback) {
this.callbacks[eventType].push(callback);
},
emitEvent: function(eventType, eventArgs) {
event = {
eventType: eventType,
args: eventArgs,
}
var callback;
for (callback of this.callbacks[eventType])
callback(event);
for (callback of this.callbacks[IBOM_EVENT_TYPES.ALL])
callback(event);
}
}
EventHandler.init();

Some files were not shown because too many files have changed in this diff Show More