@@ -126,7 +126,8 @@ def create_graph(self) -> Graph:
126
126
html = [row .replace ('<!-- connector table -->' , '\n ' .join (pinhtml )) for row in html ]
127
127
128
128
html = '\n ' .join (html )
129
- dot .node (connector .name , label = f'<\n { html } \n >' , shape = 'none' , margin = '0' , style = 'filled' , fillcolor = 'white' )
129
+ dot .node (connector .name , label = f'<\n { html } \n >' , shape = 'none' , href = connector .href ,
130
+ margin = '0' , style = 'filled' , fillcolor = 'white' )
130
131
131
132
if len (connector .loops ) > 0 :
132
133
dot .attr ('edge' , color = '#000000:#ffffff:#000000' )
@@ -243,7 +244,8 @@ def create_graph(self) -> Graph:
243
244
# connections
244
245
for connection_color in cable .connections :
245
246
if isinstance (connection_color .via_port , int ): # check if it's an actual wire and not a shield
246
- dot .attr ('edge' , color = ':' .join (['#000000' ] + wv_colors .get_color_hex (cable .colors [connection_color .via_port - 1 ], pad = pad ) + ['#000000' ]))
247
+ dot .attr ('edge' , color = ':' .join (['#000000' ] + wv_colors .get_color_hex (cable .colors [connection_color .via_port - 1 ], pad = pad ) + ['#000000' ]),
248
+ href = index_if_list (cable .href , connection_color .via_port - 1 ) if cable .href else '' )
247
249
else : # it's a shield connection
248
250
# shield is shown with specified color and black borders, or as a thin black wire otherwise
249
251
dot .attr ('edge' , color = ':' .join (['#000000' , shield_color_hex , '#000000' ]) if isinstance (cable .shield , str ) else '#000000' )
@@ -264,6 +266,7 @@ def create_graph(self) -> Graph:
264
266
265
267
html = '\n ' .join (html )
266
268
dot .node (cable .name , label = f'<\n { html } \n >' , shape = 'box' ,
269
+ href = cable .href if isinstance (cable .href , str ) else None ,
267
270
style = 'filled,dashed' if cable .category == 'bundle' else '' , margin = '0' , fillcolor = 'white' )
268
271
269
272
return dot
@@ -322,6 +325,8 @@ def output(self, filename: (str, Path), view: bool = False, cleanup: bool = True
322
325
file .write ('<tr>' )
323
326
for i , item in enumerate (row ):
324
327
item_str = item .replace ('\u00b2 ' , '²' )
328
+ if listy [0 ][i ] == 'URL' :
329
+ item_str = f'<a href="{ item } ">{ item_str } </a>'
325
330
align = 'align="right"' if listy [0 ][i ] == 'Qty' else ''
326
331
file .write (f'<td { align } style="border:1px solid #000000; padding: 4px">{ item_str } </td>' )
327
332
file .write ('</tr>' )
@@ -335,7 +340,7 @@ def bom(self):
335
340
bom_cables = []
336
341
bom_extra = []
337
342
# connectors
338
- connector_group = lambda c : (c .type , c .subtype , c .pincount , c .manufacturer , c .mpn , c .pn )
343
+ connector_group = lambda c : (c .type , c .subtype , c .pincount , c .manufacturer , c .mpn , c .pn , c . href )
339
344
for group in Counter ([connector_group (v ) for v in self .connectors .values ()]):
340
345
items = {k : v for k , v in self .connectors .items () if connector_group (v ) == group }
341
346
shared = next (iter (items .values ()))
@@ -346,15 +351,18 @@ def bom(self):
346
351
conn_pincount = f', { shared .pincount } pins' if shared .style != 'simple' else ''
347
352
conn_color = f', { shared .color } ' if shared .color else ''
348
353
name = f'Connector{ conn_type } { conn_subtype } { conn_pincount } { conn_color } '
349
- item = {'item' : name , 'qty' : len (designators ), 'unit' : '' , 'designators' : designators if shared .show_name else '' ,
350
- 'manufacturer' : remove_line_breaks (shared .manufacturer ), 'mpn' : remove_line_breaks (shared .mpn ), 'pn' : shared .pn }
354
+ item = {'item' : name , 'qty' : len (designators ), 'unit' : '' ,
355
+ 'designators' : designators if shared .show_name else '' ,
356
+ 'manufacturer' : remove_line_breaks (shared .manufacturer ),
357
+ 'mpn' : remove_line_breaks (shared .mpn ), 'pn' : shared .pn ,
358
+ 'href' : shared .href }
351
359
bom_connectors .append (item )
352
360
bom_connectors = sorted (bom_connectors , key = lambda k : k ['item' ]) # https://stackoverflow.com/a/73050
353
361
bom .extend (bom_connectors )
354
362
# cables
355
363
# TODO: If category can have other non-empty values than 'bundle', maybe it should be part of item name?
356
364
# The category needs to be included in cable_group to keep the bundles excluded.
357
- cable_group = lambda c : (c .category , c .type , c .gauge , c .gauge_unit , c .wirecount , c .shield , c .manufacturer , c .mpn , c .pn )
365
+ cable_group = lambda c : (c .category , c .type , c .gauge , c .gauge_unit , c .wirecount , c .shield , c .manufacturer , c .mpn , c .pn , c . href )
358
366
for group in Counter ([cable_group (v ) for v in self .cables .values () if v .category != 'bundle' ]):
359
367
items = {k : v for k , v in self .cables .items () if cable_group (v ) == group }
360
368
shared = next (iter (items .values ()))
@@ -365,8 +373,11 @@ def bom(self):
365
373
gauge_name = f' x { shared .gauge } { shared .gauge_unit } ' if shared .gauge else ' wires'
366
374
shield_name = ' shielded' if shared .shield else ''
367
375
name = f'Cable{ cable_type } , { shared .wirecount } { gauge_name } { shield_name } '
368
- item = {'item' : name , 'qty' : round (total_length , 3 ), 'unit' : 'm' , 'designators' : designators ,
369
- 'manufacturer' : remove_line_breaks (shared .manufacturer ), 'mpn' : remove_line_breaks (shared .mpn ), 'pn' : shared .pn }
376
+ item = {'item' : name , 'qty' : round (total_length , 3 ), 'unit' : 'm' ,
377
+ 'designators' : designators ,
378
+ 'manufacturer' : remove_line_breaks (shared .manufacturer ),
379
+ 'mpn' : remove_line_breaks (shared .mpn ), 'pn' : shared .pn ,
380
+ 'href' : shared .href }
370
381
bom_cables .append (item )
371
382
# bundles (ignores wirecount)
372
383
wirelist = []
@@ -378,9 +389,9 @@ def bom(self):
378
389
wirelist .append ({'type' : bundle .type , 'gauge' : bundle .gauge , 'gauge_unit' : bundle .gauge_unit , 'length' : bundle .length , 'color' : color , 'designator' : bundle .name ,
379
390
'manufacturer' : remove_line_breaks (index_if_list (bundle .manufacturer , index )),
380
391
'mpn' : remove_line_breaks (index_if_list (bundle .mpn , index )),
381
- 'pn' : index_if_list (bundle .pn , index )})
392
+ 'pn' : index_if_list (bundle .pn , index ), 'href' : index_if_list ( bundle . href , index ) })
382
393
# join similar wires from all the bundles to a single BOM item
383
- wire_group = lambda w : (w .get ('type' , None ), w ['gauge' ], w ['gauge_unit' ], w ['color' ], w ['manufacturer' ], w ['mpn' ], w ['pn' ])
394
+ wire_group = lambda w : (w .get ('type' , None ), w ['gauge' ], w ['gauge_unit' ], w ['color' ], w ['manufacturer' ], w ['mpn' ], w ['pn' ], w [ 'href' ] )
384
395
for group in Counter ([wire_group (v ) for v in wirelist ]):
385
396
items = [v for v in wirelist if wire_group (v ) == group ]
386
397
shared = items [0 ]
@@ -393,17 +404,18 @@ def bom(self):
393
404
gauge_color = f', { shared ["color" ]} ' if 'color' in shared != '' else ''
394
405
name = f'Wire{ wire_type } { gauge_name } { gauge_color } '
395
406
item = {'item' : name , 'qty' : round (total_length , 3 ), 'unit' : 'm' , 'designators' : designators ,
396
- 'manufacturer' : shared ['manufacturer' ], 'mpn' : shared ['mpn' ], 'pn' : shared ['pn' ]}
407
+ 'manufacturer' : shared ['manufacturer' ], 'mpn' : shared ['mpn' ], 'pn' : shared ['pn' ],
408
+ 'href' : shared ['href' ]}
397
409
bom_cables .append (item )
398
410
bom_cables = sorted (bom_cables , key = lambda k : k ['item' ]) # sort list of dicts by their values (https://stackoverflow.com/a/73050)
399
411
bom .extend (bom_cables )
400
412
401
413
for item in self .additional_bom_items :
402
- name = item ['description' ] if item .get ('description' , None ) else ''
403
- if isinstance (item .get ('designators' , None ), List ):
414
+ name = item ['description' ] if item .get ('description' ) else ''
415
+ if isinstance (item .get ('designators' ), List ):
404
416
item ['designators' ].sort () # sort designators if a list is provided
405
- item = {'item' : name , 'qty' : item .get ('qty' , None ), 'unit' : item .get ('unit' , None ), 'designators' : item .get ('designators' , None ),
406
- 'manufacturer' : item .get ('manufacturer' , None ), 'mpn' : item .get ('mpn' , None ), 'pn' : item .get ('pn' , None )}
417
+ item = {'item' : name , 'qty' : item .get ('qty' ), 'unit' : item .get ('unit' ), 'designators' : item .get ('designators' ),
418
+ 'manufacturer' : item .get ('manufacturer' ), 'mpn' : item .get ('mpn' ), 'pn' : item .get ('pn' ), 'href' : item . get ( 'href' )}
407
419
bom_extra .append (item )
408
420
bom_extra = sorted (bom_extra , key = lambda k : k ['item' ])
409
421
bom .extend (bom_extra )
@@ -412,14 +424,15 @@ def bom(self):
412
424
def bom_list (self ):
413
425
bom = self .bom ()
414
426
keys = ['item' , 'qty' , 'unit' , 'designators' ] # these BOM columns will always be included
415
- for fieldname in ['pn' , 'manufacturer' , 'mpn' ]: # these optional BOM columns will only be included if at least one BOM item actually uses them
416
- if any (fieldname in x and x .get (fieldname , None ) for x in bom ):
427
+ for fieldname in ['pn' , 'manufacturer' , 'mpn' , 'href' ]: # these optional BOM columns will only be included if at least one BOM item actually uses them
428
+ if any (item .get (fieldname ) for item in bom ):
417
429
keys .append (fieldname )
418
430
bom_list = []
419
431
# list of staic bom header names, headers not specified here are generated by capitilising the internal name
420
432
bom_headings = {
421
433
"pn" : "P/N" ,
422
- "mpn" : "MPN"
434
+ "mpn" : "MPN" ,
435
+ "href" : "URL" ,
423
436
}
424
437
bom_list .append ([(bom_headings [k ] if k in bom_headings else k .capitalize ()) for k in keys ]) # create header row with keys
425
438
for item in bom :
0 commit comments