[set_dead / skip_geometry] Improve dead pathers so more "broken" layouts can be successfully executed

This commit is contained in:
jan 2026-02-16 13:43:54 -08:00
commit f42e720c68
7 changed files with 273 additions and 73 deletions

View file

@ -284,8 +284,7 @@ class Builder(PortList):
do not line up)
"""
if self._dead:
logger.error('Skipping plug() since device is dead')
return self
logger.warning('Skipping geometry for plug() since device is dead')
if not isinstance(other, str | Abstract | Pattern):
# We got a Tree; add it into self.library and grab an Abstract for it
@ -305,6 +304,7 @@ class Builder(PortList):
set_rotation = set_rotation,
append = append,
ok_connections = ok_connections,
skip_geometry = self._dead,
)
return self
@ -357,8 +357,7 @@ class Builder(PortList):
are applied.
"""
if self._dead:
logger.error('Skipping place() since device is dead')
return self
logger.warning('Skipping geometry for place() since device is dead')
if not isinstance(other, str | Abstract | Pattern):
# We got a Tree; add it into self.library and grab an Abstract for it
@ -378,6 +377,7 @@ class Builder(PortList):
port_map = port_map,
skip_port_check = skip_port_check,
append = append,
skip_geometry = self._dead,
)
return self

View file

@ -7,6 +7,8 @@ import copy
import logging
from pprint import pformat
from numpy import pi
from ..pattern import Pattern
from ..library import ILibrary
from ..error import BuildError
@ -288,14 +290,36 @@ class Pather(Builder, PatherMixin):
LibraryError if no valid name could be picked for the pattern.
"""
if self._dead:
logger.error('Skipping path() since device is dead')
return self
logger.warning('Skipping geometry for path() since device is dead')
tool_port_names = ('A', 'B')
tool = self.tools.get(portspec, self.tools[None])
in_ptype = self.pattern[portspec].ptype
tree = tool.path(ccw, length, in_ptype=in_ptype, port_names=tool_port_names, **kwargs)
try:
tree = tool.path(ccw, length, in_ptype=in_ptype, port_names=tool_port_names, **kwargs)
except (BuildError, NotImplementedError):
if not self._dead:
raise
logger.warning("Tool path failed for dead pather. Using dummy extension.")
# Fallback for dead pather: manually update the port instead of plugging
port = self.pattern[portspec]
port_rot = port.rotation
if ccw is None:
out_rot = pi
elif bool(ccw):
out_rot = -pi / 2
else:
out_rot = pi / 2
out_port = Port((length, 0), rotation=out_rot, ptype=in_ptype)
out_port.rotate_around((0, 0), pi + port_rot)
out_port.translate(port.offset)
self.pattern.ports[portspec] = out_port
self._log_port_update(portspec)
if plug_into is not None:
self.plugged({portspec: plug_into})
return self
tname = self.library << tree
if plug_into is not None:
output = {plug_into: tool_port_names[1]}
@ -340,8 +364,7 @@ class Pather(Builder, PatherMixin):
LibraryError if no valid name could be picked for the pattern.
"""
if self._dead:
logger.error('Skipping pathS() since device is dead')
return self
logger.warning('Skipping geometry for pathS() since device is dead')
tool_port_names = ('A', 'B')
@ -353,16 +376,39 @@ class Pather(Builder, PatherMixin):
# Fall back to drawing two L-bends
ccw0 = jog > 0
kwargs_no_out = kwargs | {'out_ptype': None}
t_tree0 = tool.path( ccw0, length / 2, port_names=tool_port_names, in_ptype=in_ptype, **kwargs_no_out)
t_pat0 = t_tree0.top_pattern()
(_, jog0), _ = t_pat0[tool_port_names[0]].measure_travel(t_pat0[tool_port_names[1]])
t_tree1 = tool.path(not ccw0, abs(jog - jog0), port_names=tool_port_names, in_ptype=t_pat0[tool_port_names[1]].ptype, **kwargs)
t_pat1 = t_tree1.top_pattern()
(_, jog1), _ = t_pat1[tool_port_names[0]].measure_travel(t_pat1[tool_port_names[1]])
try:
t_tree0 = tool.path( ccw0, length / 2, port_names=tool_port_names, in_ptype=in_ptype, **kwargs_no_out)
t_pat0 = t_tree0.top_pattern()
(_, jog0), _ = t_pat0[tool_port_names[0]].measure_travel(t_pat0[tool_port_names[1]])
t_tree1 = tool.path(not ccw0, abs(jog - jog0), port_names=tool_port_names, in_ptype=t_pat0[tool_port_names[1]].ptype, **kwargs)
t_pat1 = t_tree1.top_pattern()
(_, jog1), _ = t_pat1[tool_port_names[0]].measure_travel(t_pat1[tool_port_names[1]])
kwargs_plug = kwargs | {'plug_into': plug_into}
self.path(portspec, ccw0, length - abs(jog1), **kwargs_no_out)
self.path(portspec, not ccw0, abs(jog - jog0), **kwargs_plug)
kwargs_plug = kwargs | {'plug_into': plug_into}
self.path(portspec, ccw0, length - abs(jog1), **kwargs_no_out)
self.path(portspec, not ccw0, abs(jog - jog0), **kwargs_plug)
return self
except (BuildError, NotImplementedError):
if not self._dead:
raise
# Fall through to dummy extension below
except BuildError:
if not self._dead:
raise
# Fall through to dummy extension below
if self._dead:
logger.warning("Tool pathS failed for dead pather. Using dummy extension.")
# Fallback for dead pather: manually update the port instead of plugging
port = self.pattern[portspec]
port_rot = port.rotation
out_port = Port((length, jog), rotation=pi, ptype=in_ptype)
out_port.rotate_around((0, 0), pi + port_rot)
out_port.translate(port.offset)
self.pattern.ports[portspec] = out_port
self._log_port_update(portspec)
if plug_into is not None:
self.plugged({portspec: plug_into})
return self
tname = self.library << tree

View file

@ -253,8 +253,7 @@ class RenderPather(PatherMixin):
do not line up)
"""
if self._dead:
logger.error('Skipping plug() since device is dead')
return self
logger.warning('Skipping geometry for plug() since device is dead')
other_tgt: Pattern | Abstract
if isinstance(other, str):
@ -262,18 +261,19 @@ class RenderPather(PatherMixin):
if append and isinstance(other, Abstract):
other_tgt = self.library[other.name]
# get rid of plugged ports
for kk in map_in:
if kk in self.paths:
self.paths[kk].append(RenderStep('P', None, self.ports[kk].copy(), self.ports[kk].copy(), None))
if not self._dead:
# get rid of plugged ports
for kk in map_in:
if kk in self.paths:
self.paths[kk].append(RenderStep('P', None, self.ports[kk].copy(), self.ports[kk].copy(), None))
plugged = map_in.values()
for name, port in other_tgt.ports.items():
if name in plugged:
continue
new_name = map_out.get(name, name) if map_out is not None else name
if new_name is not None and new_name in self.paths:
self.paths[new_name].append(RenderStep('P', None, port.copy(), port.copy(), None))
plugged = map_in.values()
for name, port in other_tgt.ports.items():
if name in plugged:
continue
new_name = map_out.get(name, name) if map_out is not None else name
if new_name is not None and new_name in self.paths:
self.paths[new_name].append(RenderStep('P', None, port.copy(), port.copy(), None))
self.pattern.plug(
other = other_tgt,
@ -284,6 +284,7 @@ class RenderPather(PatherMixin):
set_rotation = set_rotation,
append = append,
ok_connections = ok_connections,
skip_geometry = self._dead,
)
return self
@ -334,8 +335,7 @@ class RenderPather(PatherMixin):
are applied.
"""
if self._dead:
logger.error('Skipping place() since device is dead')
return self
logger.warning('Skipping geometry for place() since device is dead')
other_tgt: Pattern | Abstract
if isinstance(other, str):
@ -343,10 +343,11 @@ class RenderPather(PatherMixin):
if append and isinstance(other, Abstract):
other_tgt = self.library[other.name]
for name, port in other_tgt.ports.items():
new_name = port_map.get(name, name) if port_map is not None else name
if new_name is not None and new_name in self.paths:
self.paths[new_name].append(RenderStep('P', None, port.copy(), port.copy(), None))
if not self._dead:
for name, port in other_tgt.ports.items():
new_name = port_map.get(name, name) if port_map is not None else name
if new_name is not None and new_name in self.paths:
self.paths[new_name].append(RenderStep('P', None, port.copy(), port.copy(), None))
self.pattern.place(
other = other_tgt,
@ -357,6 +358,7 @@ class RenderPather(PatherMixin):
port_map = port_map,
skip_port_check = skip_port_check,
append = append,
skip_geometry = self._dead,
)
return self
@ -365,11 +367,12 @@ class RenderPather(PatherMixin):
self,
connections: dict[str, str],
) -> Self:
for aa, bb in connections.items():
porta = self.ports[aa]
portb = self.ports[bb]
self.paths[aa].append(RenderStep('P', None, porta.copy(), porta.copy(), None))
self.paths[bb].append(RenderStep('P', None, portb.copy(), portb.copy(), None))
if not self._dead:
for aa, bb in connections.items():
porta = self.ports[aa]
portb = self.ports[bb]
self.paths[aa].append(RenderStep('P', None, porta.copy(), porta.copy(), None))
self.paths[bb].append(RenderStep('P', None, portb.copy(), portb.copy(), None))
PortList.plugged(self, connections)
return self
@ -410,8 +413,7 @@ class RenderPather(PatherMixin):
LibraryError if no valid name could be picked for the pattern.
"""
if self._dead:
logger.error('Skipping path() since device is dead')
return self
logger.warning('Skipping geometry for path() since device is dead')
port = self.pattern[portspec]
in_ptype = port.ptype
@ -420,14 +422,28 @@ class RenderPather(PatherMixin):
tool = self.tools.get(portspec, self.tools[None])
# ask the tool for bend size (fill missing dx or dy), check feasibility, and get out_ptype
out_port, data = tool.planL(ccw, length, in_ptype=in_ptype, **kwargs)
try:
out_port, data = tool.planL(ccw, length, in_ptype=in_ptype, **kwargs)
except (BuildError, NotImplementedError):
if not self._dead:
raise
logger.warning("Tool planning failed for dead pather. Using dummy extension.")
if ccw is None:
out_rot = pi
elif bool(ccw):
out_rot = -pi / 2
else:
out_rot = pi / 2
out_port = Port((length, 0), rotation=out_rot, ptype=in_ptype)
data = None
# Update port
out_port.rotate_around((0, 0), pi + port_rot)
out_port.translate(port.offset)
step = RenderStep('L', tool, port.copy(), out_port.copy(), data)
self.paths[portspec].append(step)
if not self._dead:
step = RenderStep('L', tool, port.copy(), out_port.copy(), data)
self.paths[portspec].append(step)
self.pattern.ports[portspec] = out_port.copy()
self._log_port_update(portspec)
@ -475,8 +491,7 @@ class RenderPather(PatherMixin):
LibraryError if no valid name could be picked for the pattern.
"""
if self._dead:
logger.error('Skipping pathS() since device is dead')
return self
logger.warning('Skipping geometry for pathS() since device is dead')
port = self.pattern[portspec]
in_ptype = port.ptype
@ -492,22 +507,38 @@ class RenderPather(PatherMixin):
# Fall back to drawing two L-bends
ccw0 = jog > 0
kwargs_no_out = (kwargs | {'out_ptype': None})
t_port0, _ = tool.planL( ccw0, length / 2, in_ptype=in_ptype, **kwargs_no_out) # TODO length/2 may fail with asymmetric ptypes
jog0 = Port((0, 0), 0).measure_travel(t_port0)[0][1]
t_port1, _ = tool.planL(not ccw0, abs(jog - jog0), in_ptype=t_port0.ptype, **kwargs)
jog1 = Port((0, 0), 0).measure_travel(t_port1)[0][1]
try:
t_port0, _ = tool.planL( ccw0, length / 2, in_ptype=in_ptype, **kwargs_no_out) # TODO length/2 may fail with asymmetric ptypes
jog0 = Port((0, 0), 0).measure_travel(t_port0)[0][1]
t_port1, _ = tool.planL(not ccw0, abs(jog - jog0), in_ptype=t_port0.ptype, **kwargs)
jog1 = Port((0, 0), 0).measure_travel(t_port1)[0][1]
kwargs_plug = kwargs | {'plug_into': plug_into}
self.path(portspec, ccw0, length - abs(jog1), **kwargs_no_out)
self.path(portspec, not ccw0, abs(jog - jog0), **kwargs_plug)
return self
kwargs_plug = kwargs | {'plug_into': plug_into}
self.path(portspec, ccw0, length - abs(jog1), **kwargs_no_out)
self.path(portspec, not ccw0, abs(jog - jog0), **kwargs_plug)
return self
except (BuildError, NotImplementedError):
if not self._dead:
raise
# Fall through to dummy extension below
except BuildError:
if not self._dead:
raise
# Fall through to dummy extension below
out_port.rotate_around((0, 0), pi + port_rot)
out_port.translate(port.offset)
step = RenderStep('S', tool, port.copy(), out_port.copy(), data)
self.paths[portspec].append(step)
self.pattern.ports[portspec] = out_port.copy()
self._log_port_update(portspec)
if self._dead:
logger.warning("Tool planning failed for dead pather. Using dummy extension.")
out_port = Port((length, jog), rotation=pi, ptype=in_ptype)
data = None
if out_port is not None:
out_port.rotate_around((0, 0), pi + port_rot)
out_port.translate(port.offset)
if not self._dead:
step = RenderStep('S', tool, port.copy(), out_port.copy(), data)
self.paths[portspec].append(step)
self.pattern.ports[portspec] = out_port.copy()
self._log_port_update(portspec)
if plug_into is not None:
self.plugged({portspec: plug_into})