[pather] handle paths without existing ports
This commit is contained in:
parent
47f150f579
commit
02f0833fb3
2 changed files with 81 additions and 14 deletions
|
|
@ -170,24 +170,25 @@ class Pather(PortList):
|
||||||
#
|
#
|
||||||
# Core Pattern Operations (Immediate)
|
# Core Pattern Operations (Immediate)
|
||||||
#
|
#
|
||||||
def _record_break(self, names: Iterable[str | None]) -> None:
|
def _prepare_break(self, name: str | None) -> tuple[str, RenderStep] | None:
|
||||||
""" Record a batch-breaking step for the specified ports. """
|
""" Snapshot one batch-breaking step for a name with deferred geometry. """
|
||||||
if not self._dead:
|
if self._dead or name is None:
|
||||||
for n in names:
|
return None
|
||||||
if n is not None and n in self.paths:
|
|
||||||
port = self.ports[n]
|
steps = self.paths.get(name)
|
||||||
self.paths[n].append(RenderStep('P', None, port.copy(), port.copy(), None))
|
if not steps:
|
||||||
|
return None
|
||||||
|
|
||||||
|
port = self.ports.get(name, steps[-1].end_port)
|
||||||
|
return name, RenderStep('P', None, port.copy(), port.copy(), None)
|
||||||
|
|
||||||
def _prepare_breaks(self, names: Iterable[str | None]) -> list[tuple[str, RenderStep]]:
|
def _prepare_breaks(self, names: Iterable[str | None]) -> list[tuple[str, RenderStep]]:
|
||||||
""" Snapshot break markers to be committed after a successful mutation. """
|
""" Snapshot break markers to be committed after a successful mutation. """
|
||||||
if self._dead:
|
|
||||||
return []
|
|
||||||
|
|
||||||
prepared: list[tuple[str, RenderStep]] = []
|
prepared: list[tuple[str, RenderStep]] = []
|
||||||
for n in names:
|
for n in names:
|
||||||
if n is not None and n in self.paths:
|
step = self._prepare_break(n)
|
||||||
port = self.ports[n]
|
if step is not None:
|
||||||
prepared.append((n, RenderStep('P', None, port.copy(), port.copy(), None)))
|
prepared.append(step)
|
||||||
return prepared
|
return prepared
|
||||||
|
|
||||||
def _commit_breaks(self, prepared: Iterable[tuple[str, RenderStep]]) -> None:
|
def _commit_breaks(self, prepared: Iterable[tuple[str, RenderStep]]) -> None:
|
||||||
|
|
@ -246,8 +247,9 @@ class Pather(PortList):
|
||||||
|
|
||||||
@logged_op(lambda args: list(args['connections'].keys()))
|
@logged_op(lambda args: list(args['connections'].keys()))
|
||||||
def plugged(self, connections: dict[str, str]) -> Self:
|
def plugged(self, connections: dict[str, str]) -> Self:
|
||||||
self._record_break(chain(connections.keys(), connections.values()))
|
prepared_breaks = self._prepare_breaks(chain(connections.keys(), connections.values()))
|
||||||
self.pattern.plugged(connections)
|
self.pattern.plugged(connections)
|
||||||
|
self._commit_breaks(prepared_breaks)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@logged_op(lambda args: list(args['mapping'].keys()))
|
@logged_op(lambda args: list(args['mapping'].keys()))
|
||||||
|
|
|
||||||
|
|
@ -739,3 +739,68 @@ def test_pather_failed_plug_does_not_add_break_marker() -> None:
|
||||||
|
|
||||||
assert [step.opcode for step in p.paths['A']] == ['L']
|
assert [step.opcode for step in p.paths['A']] == ['L']
|
||||||
assert set(p.pattern.ports) == {'A'}
|
assert set(p.pattern.ports) == {'A'}
|
||||||
|
|
||||||
|
|
||||||
|
def test_pather_place_reused_deleted_name_keeps_break_marker() -> None:
|
||||||
|
lib = Library()
|
||||||
|
tool = PathTool(layer='M1', width=1000)
|
||||||
|
p = Pather(lib, tools=tool)
|
||||||
|
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
|
|
||||||
|
p.at('A').straight(5000)
|
||||||
|
p.rename_ports({'A': None})
|
||||||
|
|
||||||
|
other = Pattern(ports={'X': Port((-5000, 0), rotation=0)})
|
||||||
|
p.place(other, port_map={'X': 'A'}, append=True)
|
||||||
|
p.at('A').straight(2000)
|
||||||
|
|
||||||
|
assert [step.opcode for step in p.paths['A']] == ['L', 'P', 'L']
|
||||||
|
|
||||||
|
p.render()
|
||||||
|
assert p.pattern.has_shapes()
|
||||||
|
assert 'A' in p.pattern.ports
|
||||||
|
assert numpy.allclose(p.pattern.ports['A'].offset, (-7000, 0))
|
||||||
|
|
||||||
|
|
||||||
|
def test_pather_plug_reused_deleted_name_keeps_break_marker() -> None:
|
||||||
|
lib = Library()
|
||||||
|
tool = PathTool(layer='M1', width=1000)
|
||||||
|
p = Pather(lib, tools=tool)
|
||||||
|
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
|
p.pattern.ports['B'] = Port((0, 0), rotation=0)
|
||||||
|
|
||||||
|
p.at('A').straight(5000)
|
||||||
|
p.rename_ports({'A': None})
|
||||||
|
|
||||||
|
other = Pattern(
|
||||||
|
ports={
|
||||||
|
'X': Port((0, 0), rotation=pi),
|
||||||
|
'Y': Port((-5000, 0), rotation=0),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
p.plug(other, {'B': 'X'}, map_out={'Y': 'A'}, append=True)
|
||||||
|
p.at('A').straight(2000)
|
||||||
|
|
||||||
|
assert [step.opcode for step in p.paths['A']] == ['L', 'P', 'L']
|
||||||
|
|
||||||
|
p.render()
|
||||||
|
assert p.pattern.has_shapes()
|
||||||
|
assert 'A' in p.pattern.ports
|
||||||
|
assert 'B' not in p.pattern.ports
|
||||||
|
assert numpy.allclose(p.pattern.ports['A'].offset, (-7000, 0))
|
||||||
|
|
||||||
|
|
||||||
|
def test_pather_failed_plugged_does_not_add_break_marker() -> None:
|
||||||
|
lib = Library()
|
||||||
|
tool = PathTool(layer='M1', width=1000)
|
||||||
|
p = Pather(lib, tools=tool)
|
||||||
|
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
|
|
||||||
|
p.at('A').straight(5000)
|
||||||
|
assert [step.opcode for step in p.paths['A']] == ['L']
|
||||||
|
|
||||||
|
with pytest.raises(PortError, match='Connection destination ports were not found'):
|
||||||
|
p.plugged({'A': 'missing'})
|
||||||
|
|
||||||
|
assert [step.opcode for step in p.paths['A']] == ['L']
|
||||||
|
assert set(p.paths) == {'A'}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue