20
20
SingleColor ,
21
21
get_color_by_colorcode_index ,
22
22
)
23
- from wireviz .wv_utils import aspect_ratio , awg_equiv , mm2_equiv , remove_links
23
+ from wireviz .wv_utils import (
24
+ NumberAndUnit ,
25
+ awg_equiv ,
26
+ aspect_ratio ,
27
+ mm2_equiv ,
28
+ parse_number_and_unit ,
29
+ remove_links ,
30
+ )
24
31
25
32
# Each type alias have their legal values described in comments
26
33
# - validation might be implemented in the future
54
61
Side = Enum ("Side" , "LEFT RIGHT" )
55
62
ArrowDirection = Enum ("ArrowDirection" , "NONE BACK FORWARD BOTH" )
56
63
ArrowWeight = Enum ("ArrowWeight" , "SINGLE DOUBLE" )
57
- NumberAndUnit = namedtuple ("NumberAndUnit" , "number unit" )
58
64
59
65
AUTOGENERATED_PREFIX = "AUTOGENERATED_"
60
66
@@ -184,7 +190,7 @@ class Component:
184
190
supplier : str = None
185
191
spn : str = None
186
192
# BOM info
187
- qty : NumberAndUnit = NumberAndUnit ( 1 , None )
193
+ qty : Optional [ Union [ None , int , float ]] = None
188
194
amount : Optional [NumberAndUnit ] = None
189
195
sum_amounts_in_bom : bool = True
190
196
ignore_in_bom : bool = False
@@ -195,70 +201,31 @@ def __post_init__(self):
195
201
partnos = [remove_links (entry ) for entry in partnos ]
196
202
partnos = tuple (partnos )
197
203
self .partnumbers = PartNumberInfo (* partnos )
198
-
199
- self .qty = self .parse_number_and_unit (self .qty , None )
200
- self .amount = self .parse_number_and_unit (self .amount , None )
201
-
202
- def parse_number_and_unit (
203
- self ,
204
- inp : Optional [Union [NumberAndUnit , float , int , str ]],
205
- default_unit : Optional [str ] = None ,
206
- ) -> Optional [NumberAndUnit ]:
207
- if inp is None :
208
- return None
209
- elif isinstance (inp , NumberAndUnit ):
210
- return inp
211
- elif isinstance (inp , float ) or isinstance (inp , int ):
212
- return NumberAndUnit (float (inp ), default_unit )
213
- elif isinstance (inp , str ):
214
- if " " in inp :
215
- number , unit = inp .split (" " , 1 )
216
- else :
217
- number , unit = inp , default_unit
218
- try :
219
- number = float (number )
220
- except ValueError :
221
- raise Exception (
222
- f"{ inp } is not a valid number and unit.\n "
223
- "It must be a number, or a number and unit separated by a space."
224
- )
225
- else :
226
- return NumberAndUnit (number , unit )
204
+ self .amount = parse_number_and_unit (self .amount , None )
227
205
228
206
@property
229
207
def bom_hash (self ) -> BomHash :
208
+ if isinstance (self , AdditionalComponent ):
209
+ _amount = self .amount_computed
210
+ else :
211
+ _amount = self .amount
212
+
230
213
if self .sum_amounts_in_bom :
231
214
_hash = BomHash (
232
215
description = self .description ,
233
- qty_unit = self . amount . unit if self . amount else None ,
216
+ qty_unit = _amount . unit if _amount else None ,
234
217
amount = None ,
235
218
partnumbers = self .partnumbers ,
236
219
)
237
220
else :
238
221
_hash = BomHash (
239
222
description = self .description ,
240
- qty_unit = self . qty . unit ,
241
- amount = self . amount ,
223
+ qty_unit = None ,
224
+ amount = _amount ,
242
225
partnumbers = self .partnumbers ,
243
226
)
244
227
return _hash
245
228
246
- @property
247
- def bom_qty (self ) -> float :
248
- if self .sum_amounts_in_bom :
249
- if self .amount :
250
- return self .qty .number * self .amount .number
251
- else :
252
- return self .qty .number
253
- else :
254
- return self .qty .number
255
-
256
- def bom_amount (self ) -> NumberAndUnit :
257
- if self .sum_amounts_in_bom :
258
- return NumberAndUnit (None , None )
259
- else :
260
- return self .amount
261
-
262
229
@property
263
230
def has_pn_info (self ) -> bool :
264
231
return any ([self .pn , self .manufacturer , self .mpn , self .supplier , self .spn ])
@@ -292,7 +259,9 @@ def __post_init__(self):
292
259
@dataclass
293
260
class AdditionalComponent (GraphicalComponent ):
294
261
qty_multiplier : Union [QtyMultiplierConnector , QtyMultiplierCable , int ] = 1
295
- _qty_multiplier_computed : Union [int , float ] = 1
262
+ qty_computed : Optional [int ] = None
263
+ explicit_qty : bool = True
264
+ amount_computed : Optional [NumberAndUnit ] = None
296
265
note : str = None
297
266
298
267
def __post_init__ (self ):
@@ -311,9 +280,13 @@ def __post_init__(self):
311
280
else :
312
281
raise Exception (f"Unknown qty multiplier: { self .qty_multiplier } " )
313
282
314
- @property
315
- def bom_qty (self ):
316
- return self .qty .number * self ._qty_multiplier_computed
283
+ if self .qty is None and self .qty_multiplier in [
284
+ QtyMultiplierCable .TOTAL_LENGTH ,
285
+ QtyMultiplierCable .LENGTH ,
286
+ 1 ,
287
+ ]: # simplify add.comp. table in parent node for implicit qty 1
288
+ self .qty = 1
289
+ self .explicit_qty = False
317
290
318
291
319
292
@dataclass
@@ -325,8 +298,6 @@ class TopLevelGraphicalComponent(GraphicalComponent): # abstract class
325
298
additional_parameters : Optional [Dict ] = None
326
299
additional_components : List [AdditionalComponent ] = field (default_factory = list )
327
300
notes : Optional [MultilineHypertext ] = None
328
- # BOM options
329
- add_up_in_bom : Optional [bool ] = None
330
301
# rendering options
331
302
bgcolor_title : Optional [SingleColor ] = None
332
303
show_name : Optional [bool ] = None
@@ -336,7 +307,6 @@ class TopLevelGraphicalComponent(GraphicalComponent): # abstract class
336
307
class Connector (TopLevelGraphicalComponent ):
337
308
# connector-specific properties
338
309
style : Optional [str ] = None
339
- category : Optional [str ] = None
340
310
loops : List [List [Pin ]] = field (default_factory = list )
341
311
# pin information in particular
342
312
pincount : Optional [int ] = None
@@ -380,13 +350,12 @@ def __post_init__(self) -> None:
380
350
self .color = MultiColor (self .color )
381
351
382
352
# connectors do not support custom qty or amount
383
- if self .qty != NumberAndUnit (1 , None ):
353
+ if self .qty is None :
354
+ self .qty = 1
355
+ if self .qty != 1 :
384
356
raise Exception ("Connector qty != 1 not supported" )
385
357
if self .amount is not None :
386
358
raise Exception ("Connector amount not supported" )
387
- # TODO: Delete next two assignments if tests above is sufficient. Please verify!
388
- self .qty = NumberAndUnit (1 , None )
389
- self .amount = None
390
359
391
360
if isinstance (self .image , dict ):
392
361
self .image = Image (** self .image )
@@ -451,7 +420,9 @@ def __post_init__(self) -> None:
451
420
raise Exception ("Loops must be between exactly two pins!" )
452
421
for pin in loop :
453
422
if pin not in self .pins :
454
- raise Exception (f'Unknown loop pin "{ pin } " for connector "{ self .name } "!' )
423
+ raise Exception (
424
+ f'Unknown loop pin "{ pin } " for connector "{ self .name } "!'
425
+ )
455
426
# Make sure loop connected pins are not hidden.
456
427
# side=None, determine side to show loops during rendering
457
428
self .activate_pin (pin , side = None , is_connection = True )
@@ -488,7 +459,12 @@ def compute_qty_multipliers(self):
488
459
raise Exception ("Used a cable multiplier in a connector!" )
489
460
else : # int or float
490
461
computed_factor = subitem .qty_multiplier
491
- subitem ._qty_multiplier_computed = computed_factor
462
+
463
+ if subitem .qty is not None :
464
+ subitem .qty_computed = subitem .qty * computed_factor
465
+ else :
466
+ subitem .qty_computed = computed_factor
467
+ subitem .amount_computed = subitem .amount
492
468
493
469
494
470
@dataclass
@@ -623,14 +599,17 @@ def length_str(self):
623
599
@property
624
600
def bom_hash (self ):
625
601
if self .category == "bundle" :
626
- raise Exception ("Do this at the wire level!" ) # TODO
602
+ # This line should never be reached, since caller checks
603
+ # whether item is a bundle and if so, calls bom_hash
604
+ # for each individual wire instead
605
+ raise Exception ("Do this at the wire level!" )
627
606
else :
628
607
return super ().bom_hash
629
608
630
609
@property
631
610
def description (self ) -> str :
632
611
if self .category == "bundle" :
633
- raise Exception ("Do this at the wire level!" ) # TODO
612
+ raise Exception ("Do this at the wire level!" )
634
613
else :
635
614
substrs = [
636
615
("" , "Cable" ),
@@ -668,15 +647,21 @@ def __post_init__(self) -> None:
668
647
self .bgcolor_title = SingleColor (self .bgcolor_title )
669
648
self .color = MultiColor (self .color )
670
649
650
+ # cables do not support custom qty or amount
651
+ if self .qty is None :
652
+ self .qty = 1
653
+ if self .qty != 1 :
654
+ raise Exception ("Cable qty != 1 not supported" )
655
+
671
656
if isinstance (self .image , dict ):
672
657
self .image = Image (** self .image )
673
658
674
659
# TODO:
675
660
# allow gauge, length, and other fields to be lists too (like part numbers),
676
661
# and assign them the same way to bundles.
677
662
678
- self .gauge = self . parse_number_and_unit (self .gauge , "mm2" )
679
- self .length = self . parse_number_and_unit (self .length , "m" )
663
+ self .gauge = parse_number_and_unit (self .gauge , "mm2" )
664
+ self .length = parse_number_and_unit (self .length , "m" )
680
665
self .amount = self .length # for BOM
681
666
682
667
if self .wirecount : # number of wires explicitly defined
@@ -753,9 +738,11 @@ def __post_init__(self) -> None:
753
738
index = index_offset ,
754
739
id = id ,
755
740
label = "Shield" ,
756
- color = MultiColor (self .shield )
757
- if isinstance (self .shield , str )
758
- else MultiColor (None ),
741
+ color = (
742
+ MultiColor (self .shield )
743
+ if isinstance (self .shield , str )
744
+ else MultiColor (None )
745
+ ),
759
746
parent = self .designator ,
760
747
)
761
748
@@ -789,27 +776,40 @@ def compute_qty_multipliers(self):
789
776
)
790
777
qty_multipliers_computed = {
791
778
"WIRECOUNT" : len (self .wire_objects ),
792
- "TERMINATIONS" : 999 , # TODO
779
+ # "TERMINATIONS": ___ , # TODO
793
780
"LENGTH" : self .length .number if self .length else 0 ,
794
781
"TOTAL_LENGTH" : total_length ,
795
782
}
796
783
for subitem in self .additional_components :
797
784
if isinstance (subitem .qty_multiplier , QtyMultiplierCable ):
798
785
computed_factor = qty_multipliers_computed [subitem .qty_multiplier .name ]
799
- # inherit component's length unit if appropriate
800
786
if subitem .qty_multiplier .name in ["LENGTH" , "TOTAL_LENGTH" ]:
801
- if subitem .qty .unit is not None :
787
+ # since length can have a unit, use amount fields to hold
788
+ if subitem .amount is not None :
802
789
raise Exception (
803
- f"No unit may be specified when using"
804
- f"{ subitem .qty_multiplier } as a multiplier"
790
+ f"No amount may be specified when using "
791
+ f"{ subitem .qty_multiplier . name } as a multiplier. "
805
792
)
806
- subitem .qty = NumberAndUnit (subitem .qty .number , self .length .unit )
793
+ subitem .qty_computed = subitem .qty if subitem .qty else 1
794
+ subitem .amount_computed = NumberAndUnit (
795
+ computed_factor , self .length .unit
796
+ )
797
+ else :
798
+ # multiplier unrelated to length, therefore no unit
799
+ if subitem .qty is not None :
800
+ subitem .qty_computed = subitem .qty * computed_factor
801
+ else :
802
+ subitem .qty_computed = computed_factor
803
+ subitem .amount_computed = subitem .amount
807
804
808
805
elif isinstance (subitem .qty_multiplier , QtyMultiplierConnector ):
809
806
raise Exception ("Used a connector multiplier in a cable!" )
810
807
else : # int or float
811
- computed_factor = subitem .qty_multiplier
812
- subitem ._qty_multiplier_computed = computed_factor
808
+ if subitem .qty is not None :
809
+ subitem .qty_computed = subitem .qty * subitem .qty_multiplier
810
+ else :
811
+ subitem .qty_computed = subitem .qty_multiplier
812
+ subitem .amount_computed = subitem .amount
813
813
814
814
815
815
@dataclass
0 commit comments