diff --git a/masque/builder/builder.py b/masque/builder/builder.py index 40ea109..1b534b5 100644 --- a/masque/builder/builder.py +++ b/masque/builder/builder.py @@ -275,10 +275,6 @@ class Builder(PortList): Returns: self - Note: - If the builder is 'dead' (see `set_dead()`), geometry generation is - skipped but ports are still updated. - Raises: `PortError` if any ports specified in `map_in` or `map_out` do not exist in `self.ports` or `other_names`. @@ -288,7 +284,8 @@ class Builder(PortList): do not line up) """ if self._dead: - logger.warning('Skipping geometry for plug() since device is dead') + logger.error('Skipping plug() since device is dead') + return self if not isinstance(other, str | Abstract | Pattern): # We got a Tree; add it into self.library and grab an Abstract for it @@ -308,7 +305,6 @@ class Builder(PortList): set_rotation = set_rotation, append = append, ok_connections = ok_connections, - skip_geometry = self._dead, ) return self @@ -354,10 +350,6 @@ class Builder(PortList): Returns: self - Note: - If the builder is 'dead' (see `set_dead()`), geometry generation is - skipped but ports are still updated. - Raises: `PortError` if any ports specified in `map_in` or `map_out` do not exist in `self.ports` or `other.ports`. @@ -365,7 +357,8 @@ class Builder(PortList): are applied. """ if self._dead: - logger.warning('Skipping geometry for place() since device is dead') + logger.error('Skipping place() since device is dead') + return self if not isinstance(other, str | Abstract | Pattern): # We got a Tree; add it into self.library and grab an Abstract for it @@ -385,7 +378,6 @@ class Builder(PortList): port_map = port_map, skip_port_check = skip_port_check, append = append, - skip_geometry = self._dead, ) return self @@ -433,18 +425,13 @@ class Builder(PortList): def set_dead(self) -> Self: """ - Suppresses geometry generation for subsequent `plug()` and `place()` - operations. Unlike a complete skip, the port state is still tracked - and updated, using 'best-effort' fallbacks for impossible transforms. - This allows a layout script to execute through problematic sections - while maintaining valid port references for downstream code. - + Disallows further changes through `plug()` or `place()`. This is meant for debugging: ``` dev.plug(a, ...) dev.set_dead() # added for debug purposes - dev.plug(b, ...) # usually raises an error, but now uses fallback port update - dev.plug(c, ...) # also updated via fallback + dev.plug(b, ...) # usually raises an error, but now skipped + dev.plug(c, ...) # also skipped dev.pattern.visualize() # shows the device as of the set_dead() call ``` diff --git a/masque/builder/pather.py b/masque/builder/pather.py index c23e240..9af473d 100644 --- a/masque/builder/pather.py +++ b/masque/builder/pather.py @@ -7,8 +7,6 @@ import copy import logging from pprint import pformat -from numpy import pi - from ..pattern import Pattern from ..library import ILibrary from ..error import BuildError @@ -285,47 +283,19 @@ class Pather(Builder, PatherMixin): Returns: self - Note: - If the builder is 'dead', this operation will still attempt to update - the target port's location. If the pathing tool fails (e.g. due to an - impossible length), a dummy linear extension is used to maintain port - consistency for downstream operations. - Raises: BuildError if `distance` is too small to fit the bend (if a bend is present). LibraryError if no valid name could be picked for the pattern. """ if self._dead: - logger.warning('Skipping geometry for path() since device is dead') + logger.error('Skipping path() since device is dead') + return self tool_port_names = ('A', 'B') tool = self.tools.get(portspec, self.tools[None]) in_ptype = self.pattern[portspec].ptype - 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 - + tree = tool.path(ccw, length, in_ptype=in_ptype, port_names=tool_port_names, **kwargs) tname = self.library << tree if plug_into is not None: output = {plug_into: tool_port_names[1]} @@ -365,18 +335,13 @@ class Pather(Builder, PatherMixin): Returns: self - Note: - If the builder is 'dead', this operation will still attempt to update - the target port's location. If the pathing tool fails (e.g. due to an - impossible length), a dummy linear extension is used to maintain port - consistency for downstream operations. - Raises: BuildError if `distance` is too small to fit the s-bend (for nonzero jog). LibraryError if no valid name could be picked for the pattern. """ if self._dead: - logger.warning('Skipping geometry for pathS() since device is dead') + logger.error('Skipping pathS() since device is dead') + return self tool_port_names = ('A', 'B') @@ -388,39 +353,16 @@ class Pather(Builder, PatherMixin): # Fall back to drawing two L-bends ccw0 = jog > 0 kwargs_no_out = kwargs | {'out_ptype': None} - 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]]) + 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) - 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}) + 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 tname = self.library << tree diff --git a/masque/builder/renderpather.py b/masque/builder/renderpather.py index ca8cf8a..29a8173 100644 --- a/masque/builder/renderpather.py +++ b/masque/builder/renderpather.py @@ -253,7 +253,8 @@ class RenderPather(PatherMixin): do not line up) """ if self._dead: - logger.warning('Skipping geometry for plug() since device is dead') + logger.error('Skipping plug() since device is dead') + return self other_tgt: Pattern | Abstract if isinstance(other, str): @@ -261,19 +262,18 @@ class RenderPather(PatherMixin): if append and isinstance(other, Abstract): other_tgt = self.library[other.name] - 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)) + # 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,7 +284,6 @@ class RenderPather(PatherMixin): set_rotation = set_rotation, append = append, ok_connections = ok_connections, - skip_geometry = self._dead, ) return self @@ -335,7 +334,8 @@ class RenderPather(PatherMixin): are applied. """ if self._dead: - logger.warning('Skipping geometry for place() since device is dead') + logger.error('Skipping place() since device is dead') + return self other_tgt: Pattern | Abstract if isinstance(other, str): @@ -343,11 +343,10 @@ class RenderPather(PatherMixin): if append and isinstance(other, Abstract): other_tgt = self.library[other.name] - 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)) + 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, @@ -358,7 +357,6 @@ class RenderPather(PatherMixin): port_map = port_map, skip_port_check = skip_port_check, append = append, - skip_geometry = self._dead, ) return self @@ -367,12 +365,11 @@ class RenderPather(PatherMixin): self, connections: dict[str, str], ) -> Self: - 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)) + 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 @@ -408,18 +405,13 @@ class RenderPather(PatherMixin): Returns: self - Note: - If the builder is 'dead', this operation will still attempt to update - the target port's location. If the pathing tool fails (e.g. due to an - impossible length), a dummy linear extension is used to maintain port - consistency for downstream operations. - Raises: BuildError if `distance` is too small to fit the bend (if a bend is present). LibraryError if no valid name could be picked for the pattern. """ if self._dead: - logger.warning('Skipping geometry for path() since device is dead') + logger.error('Skipping path() since device is dead') + return self port = self.pattern[portspec] in_ptype = port.ptype @@ -428,31 +420,16 @@ 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 - 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 + out_port, data = tool.planL(ccw, length, in_ptype=in_ptype, **kwargs) # Update port out_port.rotate_around((0, 0), pi + port_rot) out_port.translate(port.offset) - if not self._dead: - step = RenderStep('L', tool, port.copy(), out_port.copy(), data) - self.paths[portspec].append(step) + 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) if plug_into is not None: self.plugged({portspec: plug_into}) @@ -492,18 +469,13 @@ class RenderPather(PatherMixin): Returns: self - Note: - If the builder is 'dead', this operation will still attempt to update - the target port's location. If the pathing tool fails (e.g. due to an - impossible length), a dummy linear extension is used to maintain port - consistency for downstream operations. - Raises: BuildError if `distance` is too small to fit the s-bend (for nonzero jog). LibraryError if no valid name could be picked for the pattern. """ if self._dead: - logger.warning('Skipping geometry for pathS() since device is dead') + logger.error('Skipping pathS() since device is dead') + return self port = self.pattern[portspec] in_ptype = port.ptype @@ -519,38 +491,21 @@ class RenderPather(PatherMixin): # Fall back to drawing two L-bends ccw0 = jog > 0 kwargs_no_out = (kwargs | {'out_ptype': None}) - 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] + 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 - 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 + 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 - 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) + 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() if plug_into is not None: self.plugged({portspec: plug_into}) diff --git a/masque/pattern.py b/masque/pattern.py index d7bbc01..94555cc 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -638,7 +638,6 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): """ for entry in chain(chain_elements(self.shapes, self.labels, self.refs), self.ports.values()): cast('Positionable', entry).translate(offset) - self._log_bulk_update(f"translate({offset})") return self def scale_elements(self, c: float) -> Self: @@ -706,7 +705,6 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): self.rotate_elements(rotation) self.rotate_element_centers(rotation) self.translate_elements(+pivot) - self._log_bulk_update(f"rotate_around({pivot}, {rotation})") return self def rotate_element_centers(self, rotation: float) -> Self: @@ -763,7 +761,6 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): """ for entry in chain(chain_elements(self.shapes, self.refs, self.labels), self.ports.values()): cast('Flippable', entry).flip_across(axis=axis) - self._log_bulk_update(f"mirror({axis})") return self def copy(self) -> Self: @@ -1101,7 +1098,6 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): port_map: dict[str, str | None] | None = None, skip_port_check: bool = False, append: bool = False, - skip_geometry: bool = False, ) -> Self: """ Instantiate or append the pattern `other` into the current pattern, adding its @@ -1133,10 +1129,6 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): append: If `True`, `other` is appended instead of being referenced. Note that this does not flatten `other`, so its refs will still be refs (now inside `self`). - skip_geometry: If `True`, the operation only updates the port list and - skips adding any geometry (shapes, labels, or references). This - allows the pattern assembly to proceed for port-tracking purposes - even when layout generation is suppressed. Returns: self @@ -1168,10 +1160,6 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): pp.rotate_around(pivot, rotation) pp.translate(offset) self.ports[name] = pp - self._log_port_update(name) - - if skip_geometry: - return self if append: if isinstance(other, Abstract): @@ -1230,7 +1218,6 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): set_rotation: bool | None = None, append: bool = False, ok_connections: Iterable[tuple[str, str]] = (), - skip_geometry: bool = False, ) -> Self: """ Instantiate or append a pattern into the current pattern, connecting @@ -1285,11 +1272,6 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): any other ptypte. Non-allowed ptype connections will emit a warning. Order is ignored, i.e. `(a, b)` is equivalent to `(b, a)`. - skip_geometry: If `True`, only ports are updated and geometry is - skipped. If a valid transform cannot be found (e.g. due to - misaligned ports), a 'best-effort' dummy transform is used - to ensure new ports are still added at approximate locations, - allowing downstream routing to continue. Returns: self @@ -1322,41 +1304,21 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): map_out = {out_port_name: next(iter(map_in.keys()))} self.check_ports(other.ports.keys(), map_in, map_out) - try: - translation, rotation, pivot = self.find_transform( - other, - map_in, - mirrored = mirrored, - set_rotation = set_rotation, - ok_connections = ok_connections, - ) - except PortError: - if not skip_geometry: - raise - logger.warning("Port transform failed for dead device. Using dummy transform.") - if map_in: - ki, vi = next(iter(map_in.items())) - s_port = self.ports[ki] - o_port = other.ports[vi].deepcopy() - if mirrored: - o_port.mirror() - o_port.offset[1] *= -1 - translation = s_port.offset - o_port.offset - rotation = (s_port.rotation - o_port.rotation - pi) if (s_port.rotation is not None and o_port.rotation is not None) else 0 - pivot = o_port.offset - else: - translation = numpy.zeros(2) - rotation = 0.0 - pivot = numpy.zeros(2) + translation, rotation, pivot = self.find_transform( + other, + map_in, + mirrored = mirrored, + set_rotation = set_rotation, + ok_connections = ok_connections, + ) # get rid of plugged ports for ki, vi in map_in.items(): del self.ports[ki] - self._log_port_removal(ki) map_out[vi] = None if isinstance(other, Pattern): - assert append or skip_geometry, 'Got a name (not an abstract) but was asked to reference (not append)' + assert append, 'Got a name (not an abstract) but was asked to reference (not append)' self.place( other, @@ -1367,7 +1329,6 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): port_map = map_out, skip_port_check = True, append = append, - skip_geometry = skip_geometry, ) return self diff --git a/masque/ports.py b/masque/ports.py index 5260b19..45aedb5 100644 --- a/masque/ports.py +++ b/masque/ports.py @@ -17,7 +17,6 @@ from .error import PortError, format_stacktrace logger = logging.getLogger(__name__) -port_logger = logging.getLogger('masque.ports') @functools.total_ordering @@ -208,19 +207,6 @@ class PortList(metaclass=ABCMeta): def ports(self, value: dict[str, Port]) -> None: pass - def _log_port_update(self, name: str) -> None: - """ Log the current state of the named port """ - port_logger.info("Port %s: %s", name, self.ports[name]) - - def _log_port_removal(self, name: str) -> None: - """ Log that the named port has been removed """ - port_logger.info("Port %s: removed", name) - - def _log_bulk_update(self, label: str) -> None: - """ Log all current ports at DEBUG level """ - for name, port in self.ports.items(): - port_logger.debug("%s: Port %s: %s", label, name, port) - @overload def __getitem__(self, key: str) -> Port: pass @@ -274,7 +260,6 @@ class PortList(metaclass=ABCMeta): raise PortError(f'Port {name} already exists.') assert name not in self.ports self.ports[name] = value - self._log_port_update(name) return self def rename_ports( @@ -301,22 +286,11 @@ class PortList(metaclass=ABCMeta): if duplicates: raise PortError(f'Unrenamed ports would be overwritten: {duplicates}') - for kk, vv in mapping.items(): - if vv is None: - self._log_port_removal(kk) - elif vv != kk: - self._log_port_removal(kk) - renamed = {vv: self.ports.pop(kk) for kk, vv in mapping.items()} if None in renamed: del renamed[None] self.ports.update(renamed) # type: ignore - - for vv in mapping.values(): - if vv is not None: - self._log_port_update(vv) - return self def add_port_pair( @@ -345,8 +319,6 @@ class PortList(metaclass=ABCMeta): } self.check_ports(names) self.ports.update(new_ports) - self._log_port_update(names[0]) - self._log_port_update(names[1]) return self def plugged( @@ -416,7 +388,6 @@ class PortList(metaclass=ABCMeta): for pp in chain(a_names, b_names): del self.ports[pp] - self._log_port_removal(pp) return self def check_ports( diff --git a/masque/test/test_builder.py b/masque/test/test_builder.py index bfbd1df..1b67c65 100644 --- a/masque/test/test_builder.py +++ b/masque/test/test_builder.py @@ -73,57 +73,3 @@ def test_builder_set_dead() -> None: b.place("sub") assert not b.pattern.has_refs() - - -def test_builder_dead_ports() -> None: - lib = Library() - pat = Pattern() - pat.ports['A'] = Port((0, 0), 0) - b = Builder(lib, pattern=pat) - b.set_dead() - - # Attempt to plug a device where ports don't line up - # A has rotation 0, C has rotation 0. plug() expects opposing rotations (pi difference). - other = Pattern(ports={'C': Port((10, 10), 0), 'D': Port((20, 20), 0)}) - - # This should NOT raise PortError because b is dead - b.plug(other, map_in={'A': 'C'}, map_out={'D': 'B'}) - - # Port A should be removed, and Port B (renamed from D) should be added - assert 'A' not in b.ports - assert 'B' in b.ports - - # Verify geometry was not added - assert not b.pattern.has_refs() - assert not b.pattern.has_shapes() - - -def test_dead_plug_best_effort() -> None: - lib = Library() - pat = Pattern() - pat.ports['A'] = Port((0, 0), 0) - b = Builder(lib, pattern=pat) - b.set_dead() - - # Device with multiple ports, none of which line up correctly - other = Pattern(ports={ - 'P1': Port((10, 10), 0), # Wrong rotation (0 instead of pi) - 'P2': Port((20, 20), pi) # Correct rotation but wrong offset - }) - - # Try to plug. find_transform will fail. - # It should fall back to aligning the first pair ('A' and 'P1'). - b.plug(other, map_in={'A': 'P1'}, map_out={'P2': 'B'}) - - assert 'A' not in b.ports - assert 'B' in b.ports - - # Dummy transform aligns A (0,0) with P1 (10,10) - # A rotation 0, P1 rotation 0 -> rotation = (0 - 0 - pi) = -pi - # P2 (20,20) rotation pi: - # 1. Translate P2 so P1 is at origin: (20,20) - (10,10) = (10,10) - # 2. Rotate (10,10) by -pi: (-10,-10) - # 3. Translate by s_port.offset (0,0): (-10,-10) - assert_allclose(b.ports['B'].offset, [-10, -10], atol=1e-10) - # P2 rot pi + transform rot -pi = 0 - assert_allclose(b.ports['B'].rotation, 0, atol=1e-10) diff --git a/masque/test/test_pather.py b/masque/test/test_pather.py index 336458f..e1d28d8 100644 --- a/masque/test/test_pather.py +++ b/masque/test/test_pather.py @@ -81,25 +81,3 @@ def test_pather_at_chaining(pather_setup: tuple[Pather, PathTool, Library]) -> N # pi/2 (North) + CCW (90 deg) -> 0 (East)? # Actual behavior results in pi (West). assert_allclose(p.ports["start"].rotation, pi, atol=1e-10) - - -def test_pather_dead_ports() -> None: - lib = Library() - tool = PathTool(layer=(1, 0), width=1) - p = Pather(lib, ports={"in": Port((0, 0), 0)}, tools=tool) - p.set_dead() - - # Path with negative length (impossible for PathTool, would normally raise BuildError) - p.path("in", None, -10) - - # Port 'in' should be updated by dummy extension despite tool failure - # port_rot=0, forward is -x. path(-10) means moving -10 in -x direction -> +10 in x. - assert_allclose(p.ports["in"].offset, [10, 0], atol=1e-10) - - # Downstream path should work correctly using the dummy port location - p.path("in", None, 20) - # 10 + (-20) = -10 - assert_allclose(p.ports["in"].offset, [-10, 0], atol=1e-10) - - # Verify no geometry - assert not p.pattern.has_shapes() diff --git a/masque/test/test_renderpather.py b/masque/test/test_renderpather.py index 3948214..b843066 100644 --- a/masque/test/test_renderpather.py +++ b/masque/test/test_renderpather.py @@ -73,23 +73,3 @@ def test_renderpather_retool(rpather_setup: tuple[RenderPather, PathTool, Librar # Different tools should cause different batches/shapes assert len(rp.pattern.shapes[(1, 0)]) == 1 assert len(rp.pattern.shapes[(2, 0)]) == 1 - - -def test_renderpather_dead_ports() -> None: - lib = Library() - tool = PathTool(layer=(1, 0), width=1) - rp = RenderPather(lib, ports={"in": Port((0, 0), 0)}, tools=tool) - rp.set_dead() - - # Impossible path - rp.path("in", None, -10) - - # port_rot=0, forward is -x. path(-10) means moving -10 in -x direction -> +10 in x. - assert_allclose(rp.ports["in"].offset, [10, 0], atol=1e-10) - - # Verify no render steps were added - assert len(rp.paths["in"]) == 0 - - # Verify no geometry - rp.render() - assert not rp.pattern.has_shapes()