@@ -620,17 +620,16 @@ def _snap_two_pin_parts(node):
620620 _stagger_tjunctions (node , node_part_ids , snapped , occupied_pins )
621621
622622
623- def _stagger_tjunctions (node , node_part_ids , snapped , occupied_pins , min_group = 3 ):
623+ def _stagger_tjunctions (node , node_part_ids , snapped , occupied_pins , min_group = 2 ):
624624 """Detect repeating T-junction patterns and stagger parts outward from IC.
625625
626626 A T-junction occurs when 2+ two-pin parts share a signal net with the same
627627 IC pin. When 3+ IC pins on the same IC have the same fan-out count, it's a
628628 repeating pattern. Parts are rearranged so each pin's group steps further
629629 from the IC body, with all parts extending perpendicular.
630630
631- Also moves the pass-1 snapped part from its overlapping position to the
632- staggered position, and records junction wires on node._tjunction_wires
633- for sexp_schematic to render.
631+ After staggering, IC groups are vertically redistributed so their stagger
632+ fans don't overlap.
634633 """
635634 perp_map = {"R" : "D" , "L" : "U" , "U" : "R" , "D" : "L" }
636635 anti_perp = {"U" : "D" , "D" : "U" , "L" : "R" , "R" : "L" }
@@ -679,6 +678,9 @@ def _stagger_tjunctions(node, node_part_ids, snapped, occupied_pins, min_group=3
679678 junction_wires = getattr (node , "_tjunction_wires" , [])
680679 suppressed_pins = set ()
681680
681+ # Collect stagger group metadata for redistribution pass.
682+ stagger_groups = []
683+
682684 for ic_id , pin_entries in ic_groups .items ():
683685 fanout_counts = [len (pl ) for _ , pl in pin_entries ]
684686 dominant = max (set (fanout_counts ), key = fanout_counts .count )
@@ -707,10 +709,10 @@ def _stagger_tjunctions(node, node_part_ids, snapped, occupied_pins, min_group=3
707709 max_span = max (max_span , span )
708710 step_size = max (100 , int (max_span ) + 50 )
709711
710- def _pin_sort_key (entry ):
712+ def _pin_sort_key (entry , _ic_part = ic_part , _ic_dir = ic_dir ):
711713 ic_pin = entry [0 ]
712- w = ic_pin .pt * ic_part .tx
713- if ic_dir in ("L" , "R" ):
714+ w = ic_pin .pt * _ic_part .tx
715+ if _ic_dir in ("L" , "R" ):
714716 return w .y
715717 return w .x
716718
@@ -720,6 +722,9 @@ def _pin_sort_key(entry):
720722 anti = anti_perp .get (perp_dir , perp_dir )
721723 extend_dirs = [perp_dir , anti ] if parts_per_pin >= 2 else [perp_dir ]
722724
725+ group_parts = []
726+ group_wires = []
727+
723728 for pin_idx , (ic_pin , parts_list ) in enumerate (matching ):
724729 ic_pin_world = ic_pin .pt * ic_part .tx
725730
@@ -737,15 +742,109 @@ def _pin_sort_key(entry):
737742 )
738743 snapped .add (id (part ))
739744 suppressed_pins .add (id (my_pin ))
745+ group_parts .append (part )
740746
741- junction_wires .append (
747+ group_wires .append (
742748 (ic_pin_world .x , ic_pin_world .y , ox , oy )
743749 )
744750
751+ stagger_groups .append ({
752+ "ic_part" : ic_part ,
753+ "parts" : group_parts ,
754+ "wires" : group_wires ,
755+ "ic_dir" : ic_dir ,
756+ "n_pins" : len (matching ),
757+ "step_size" : step_size ,
758+ })
759+
760+ # Redistribute IC groups vertically so stagger fans don't overlap.
761+ if len (stagger_groups ) > 1 :
762+ _redistribute_stagger_groups (stagger_groups , node , snapped )
763+
764+ for grp in stagger_groups :
765+ junction_wires .extend (grp ["wires" ])
766+
745767 node ._tjunction_wires = junction_wires
746768 node ._tjunction_suppressed_pins = suppressed_pins
747769
748770
771+ def _redistribute_stagger_groups (groups , node , snapped ):
772+ """Shift IC groups vertically so their stagger fans don't overlap."""
773+
774+ for grp in groups :
775+ _collect_ic_dependents (grp , node , snapped )
776+
777+ def _group_bbox (grp ):
778+ all_parts = [grp ["ic_part" ]] + grp ["all_deps" ]
779+ min_y = float ("inf" )
780+ max_y = float ("-inf" )
781+
782+ for part in all_parts :
783+ for pin in part .pins :
784+ w = pin .pt * part .tx
785+ min_y = min (min_y , w .y )
786+ max_y = max (max_y , w .y )
787+
788+ return min_y , max_y
789+
790+ groups .sort (key = lambda g : _group_bbox (g )[0 ])
791+
792+ margin = 200
793+ prev_max_y = None
794+
795+ for grp in groups :
796+ min_y , max_y = _group_bbox (grp )
797+
798+ if prev_max_y is not None and min_y < prev_max_y + margin :
799+ shift = (prev_max_y + margin ) - min_y
800+ _shift_group (grp , 0 , shift )
801+ min_y += shift
802+ max_y += shift
803+
804+ prev_max_y = max_y
805+
806+
807+ def _collect_ic_dependents (grp , node , snapped ):
808+ """Find all snapped 2-pin parts connected to this IC (not just stagger parts)."""
809+ ic = grp ["ic_part" ]
810+ ic_id = id (ic )
811+ deps = set (id (p ) for p in grp ["parts" ])
812+ deps .add (ic_id )
813+
814+ for part in node .parts :
815+ if id (part ) in deps or id (part ) not in snapped :
816+ continue
817+ if not _is_two_pin_part (part ):
818+ continue
819+ for pin in part .pins :
820+ net = getattr (pin , "net" , None )
821+ if not net :
822+ continue
823+ for net_pin in net .pins :
824+ if net_pin .part is ic :
825+ deps .add (id (part ))
826+ break
827+ if id (part ) in deps :
828+ break
829+
830+ grp ["all_deps" ] = [p for p in node .parts if id (p ) in deps and p is not ic ]
831+
832+
833+ def _shift_group (grp , dx , dy ):
834+ """Shift an IC and all its dependent parts by (dx, dy)."""
835+ vec = Point (dx , dy )
836+ all_parts = [grp ["ic_part" ]] + grp ["all_deps" ]
837+ shifted_ids = set ()
838+ for part in all_parts :
839+ if id (part ) not in shifted_ids :
840+ part .tx = part .tx .move (vec )
841+ shifted_ids .add (id (part ))
842+ grp ["wires" ] = [
843+ (x1 + dx , y1 + dy , x2 + dx , y2 + dy )
844+ for (x1 , y1 , x2 , y2 ) in grp ["wires" ]
845+ ]
846+
847+
749848def _stub_all_non_explicit (circuit ):
750849 """Stub all nets that weren't explicitly set by the user (labels-only fallback).
751850
0 commit comments