Generalize underscore into SINGLE_USE_PREFIX

This commit is contained in:
jan 2023-10-15 23:01:47 -07:00
parent 668d4b5d8b
commit 1bdb998085
3 changed files with 39 additions and 32 deletions

View File

@ -11,7 +11,7 @@ from numpy import pi
from numpy.typing import ArrayLike from numpy.typing import ArrayLike
from ..pattern import Pattern from ..pattern import Pattern
from ..library import ILibrary from ..library import ILibrary, SINGLE_USE_PREFIX
from ..error import PortError, BuildError from ..error import PortError, BuildError
from ..ports import PortList, Port from ..ports import PortList, Port
from ..abstract import Abstract from ..abstract import Abstract
@ -422,7 +422,7 @@ class Pather(Builder):
set_rotation: float | None = None, set_rotation: float | None = None,
tool_port_names: tuple[str, str] = ('A', 'B'), tool_port_names: tuple[str, str] = ('A', 'B'),
force_container: bool = False, force_container: bool = False,
base_name: str = '_mpath', base_name: str = SINGLE_USE_PREFIX + 'mpath',
**kwargs, **kwargs,
) -> Self: ) -> Self:
""" """

View File

@ -15,7 +15,7 @@ from ..utils import SupportsBool, rotation_matrix_2d, layer_t
from ..ports import Port from ..ports import Port
from ..pattern import Pattern from ..pattern import Pattern
from ..abstract import Abstract from ..abstract import Abstract
from ..library import ILibrary, Library from ..library import ILibrary, Library, SINGLE_USE_PREFIX
from ..error import BuildError from ..error import BuildError
@ -288,13 +288,13 @@ class BasicTool(Tool, metaclass=ABCMeta):
) )
gen_straight, sport_in, sport_out = self.straight gen_straight, sport_in, sport_out = self.straight
tree, pat = Library.mktree('_path') tree, pat = Library.mktree(SINGLE_USE_PREFIX + 'path')
pat.add_port_pair(names=port_names) pat.add_port_pair(names=port_names)
if data.in_transition: if data.in_transition:
ipat, iport_theirs, _iport_ours = data.in_transition ipat, iport_theirs, _iport_ours = data.in_transition
pat.plug(ipat, {port_names[1]: iport_theirs}) pat.plug(ipat, {port_names[1]: iport_theirs})
if not numpy.isclose(data.straight_length, 0): if not numpy.isclose(data.straight_length, 0):
straight = tree <= {'_straight': gen_straight(data.straight_length)} straight = tree <= {SINGLE_USE_PREFIX + 'straight': gen_straight(data.straight_length)}
pat.plug(straight, {port_names[1]: sport_in}) pat.plug(straight, {port_names[1]: sport_in})
if data.ccw is not None: if data.ccw is not None:
bend, bport_in, bport_out = self.bend bend, bport_in, bport_out = self.bend
@ -391,7 +391,7 @@ class BasicTool(Tool, metaclass=ABCMeta):
**kwargs, **kwargs,
) -> ILibrary: ) -> ILibrary:
tree, pat = Library.mktree('_path') tree, pat = Library.mktree(SINGLE_USE_PREFIX + 'path')
pat.add_port_pair(names=(port_names[0], port_names[1])) pat.add_port_pair(names=(port_names[0], port_names[1]))
gen_straight, sport_in, _sport_out = self.straight gen_straight, sport_in, _sport_out = self.straight
@ -408,7 +408,7 @@ class BasicTool(Tool, metaclass=ABCMeta):
if append: if append:
pat.plug(straight_pat, {port_names[1]: sport_in}, append=True) pat.plug(straight_pat, {port_names[1]: sport_in}, append=True)
else: else:
straight = tree <= {'_straight': straight_pat} straight = tree <= {SINGLE_USE_PREFIX + 'straight': straight_pat}
pat.plug(straight, {port_names[1]: sport_in}, append=True) pat.plug(straight, {port_names[1]: sport_in}, append=True)
if ccw is not None: if ccw is not None:
bend, bport_in, bport_out = self.bend bend, bport_in, bport_out = self.bend
@ -463,7 +463,7 @@ class PathTool(Tool, metaclass=ABCMeta):
out_ptype=out_ptype, out_ptype=out_ptype,
) )
tree, pat = Library.mktree('_path') tree, pat = Library.mktree(SINGLE_USE_PREFIX + 'path')
pat.path(layer=self.layer, width=self.width, vertices=[(0, 0), (length, 0)]) pat.path(layer=self.layer, width=self.width, vertices=[(0, 0), (length, 0)])
if ccw is None: if ccw is None:
@ -543,7 +543,7 @@ class PathTool(Tool, metaclass=ABCMeta):
# If the path ends in a bend, we need to add the final vertex # If the path ends in a bend, we need to add the final vertex
path_vertices.append(batch[-1].end_port.offset) path_vertices.append(batch[-1].end_port.offset)
tree, pat = Library.mktree('_path') tree, pat = Library.mktree(SINGLE_USE_PREFIX + 'path')
pat.path(layer=self.layer, width=self.width, vertices=path_vertices) pat.path(layer=self.layer, width=self.width, vertices=path_vertices)
pat.ports = { pat.ports = {
port_names[0]: batch[0].start_port.copy().rotate(pi), port_names[0]: batch[0].start_port.copy().rotate(pi),

View File

@ -35,15 +35,24 @@ logger = logging.getLogger(__name__)
visitor_function_t = Callable[..., 'Pattern'] visitor_function_t = Callable[..., 'Pattern']
SINGLE_USE_PREFIX = '_'
"""
Names starting with this prefix are assumed to refer to single-use patterns,
which may be renamed automatically by `ILibrary.add()` (via
`rename_theirs=_rename_patterns()` )
"""
# TODO what are the consequences of making '_' special? maybe we can make this decision everywhere?
def _rename_patterns(lib: 'ILibraryView', name: str) -> str: def _rename_patterns(lib: 'ILibraryView', name: str) -> str:
""" """
The default `rename_theirs` function for `ILibrary.add`. The default `rename_theirs` function for `ILibrary.add`.
Treats names starting with an underscore as "one-offs" for which name conflicts Treats names starting with `SINGLE_USE_PREFIX` (default: one underscore) as
should be automatically resolved. Conflicts are resolved by calling "one-offs" for which name conflicts should be automatically resolved.
`lib.get_name(name.split('$')[0])`. Conflicts are resolved by calling `lib.get_name(SINGLE_USE_PREFIX + stem)`
Names without a leading underscore are directly returned. where `stem = name.removeprefix(SINGLE_USE_PREFIX).split('$')[0]`.
Names lacking the prefix are directly returned (not renamed).
Args: Args:
lib: The library into which `name` is to be added (but is presumed to conflict) lib: The library into which `name` is to be added (but is presumed to conflict)
@ -52,11 +61,11 @@ def _rename_patterns(lib: 'ILibraryView', name: str) -> str:
Returns: Returns:
The new name, not guaranteed to be conflict-free! The new name, not guaranteed to be conflict-free!
""" """
if not name.startswith('_'): # TODO what are the consequences of making '_' special? maybe we can make this decision everywhere? if not name.startswith(SINGLE_USE_PREFIX):
return name return name
stem = name.split('$')[0] stem = name.removeprefix(SINGLE_USE_PREFIX).split('$')[0]
return lib.get_name(stem) return lib.get_name(SINGLE_USE_PREFIX + stem)
class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta): class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta):
@ -284,7 +293,7 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta):
def get_name( def get_name(
self, self,
name: str = '__', name: str = SINGLE_USE_PREFIX * 2,
sanitize: bool = True, sanitize: bool = True,
max_length: int = 32, max_length: int = 32,
quiet: bool | None = None, quiet: bool | None = None,
@ -295,17 +304,17 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta):
This function may be overridden in a subclass or monkey-patched to fit the caller's requirements. This function may be overridden in a subclass or monkey-patched to fit the caller's requirements.
Args: Args:
name: Preferred name for the pattern. Default '__'. name: Preferred name for the pattern. Default is `SINGLE_USE_PREFIX * 2`.
sanitize: Allows only alphanumeric charaters and _?$. Replaces invalid characters with underscores. sanitize: Allows only alphanumeric charaters and _?$. Replaces invalid characters with underscores.
max_length: Names longer than this will be truncated. max_length: Names longer than this will be truncated.
quiet: If `True`, suppress log messages. Default `None` suppresses messages only if quiet: If `True`, suppress log messages. Default `None` suppresses messages only if
the name starts with an underscore. the name starts with `SINGLE_USE_PREFIX`.
Returns: Returns:
Name, unique within this library. Name, unique within this library.
""" """
if quiet is None: if quiet is None:
quiet = name.startswith('_') quiet = name.startswith(SINGLE_USE_PREFIX)
if sanitize: if sanitize:
# Remove invalid characters # Remove invalid characters
@ -316,7 +325,7 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta):
ii = 0 ii = 0
suffixed_name = sanitized_name suffixed_name = sanitized_name
while suffixed_name in self or suffixed_name == '': while suffixed_name in self or suffixed_name == '':
suffix = base64.b64encode(struct.pack('>Q', ii), b'$?').decode('ASCII') suffix = base64.b64encode(struct.pack('>Q', ii), altchars=b'$?').decode('ASCII')
suffixed_name = sanitized_name + '$' + suffix[:-1].lstrip('A') suffixed_name = sanitized_name + '$' + suffix[:-1].lstrip('A')
ii += 1 ii += 1
@ -602,16 +611,14 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta):
If `mutate_other=False` (default), all changes are made to a deepcopy of `other`. If `mutate_other=False` (default), all changes are made to a deepcopy of `other`.
By default, `rename_theirs` makes no changes to the name (causing a `LibraryError`) unless the By default, `rename_theirs` makes no changes to the name (causing a `LibraryError`) unless the
name starts with an underscore. Underscored names are truncated to before their first '$' name starts with `SINGLE_USE_PREFIX`. Prefixed names are truncated to before their first
and then passed to `self.get_name()` to create a new unique name. non-prefix '$' and then passed to `self.get_name()` to create a new unique name.
Args: Args:
other: The library to insert keys from. other: The library to insert keys from.
rename_theirs: Called as rename_theirs(self, name) for each duplicate name rename_theirs: Called as rename_theirs(self, name) for each duplicate name
encountered in `other`. Should return the new name for the pattern in encountered in `other`. Should return the new name for the pattern in
`other`. `other`. See above for default behavior.
Default is effectively
`self.get_name(name.split('$')[0]) if name.startswith('_') else name`
mutate_other: If `True`, modify the original library and its contained patterns mutate_other: If `True`, modify the original library and its contained patterns
(e.g. when renaming patterns and updating refs). Otherwise, operate on a deepcopy (e.g. when renaming patterns and updating refs). Otherwise, operate on a deepcopy
(default). (default).
@ -703,7 +710,8 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta):
exclude_types: Shape types passed in this argument are always left untouched, for exclude_types: Shape types passed in this argument are always left untouched, for
speed or convenience. Default: `(shapes.Polygon,)` speed or convenience. Default: `(shapes.Polygon,)`
label2name: Given a label tuple as returned by `shape.normalized_form(...)`, pick label2name: Given a label tuple as returned by `shape.normalized_form(...)`, pick
a name for the generated pattern. Default `self.get_name('_shape')`. a name for the generated pattern.
Default `self.get_name(SINGLE_USE_PREIX + 'shape')`.
threshold: Only replace shapes with refs if there will be at least this many threshold: Only replace shapes with refs if there will be at least this many
instances. instances.
@ -720,8 +728,7 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta):
if label2name is None: if label2name is None:
def label2name(label): def label2name(label):
return self.get_name('_shape') return self.get_name(SINGLE_USE_PREFIX + 'shape')
#label2name = lambda label: self.get_name('_shape')
shape_counts: MutableMapping[tuple, int] = defaultdict(int) shape_counts: MutableMapping[tuple, int] = defaultdict(int)
shape_funcs = {} shape_funcs = {}
@ -801,7 +808,8 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta):
Args: Args:
name_func: Function f(this_pattern, shape) which generates a name for the name_func: Function f(this_pattern, shape) which generates a name for the
wrapping pattern. Default is `self.get_name('_rep')`. wrapping pattern.
Default is `self.get_name(SINGLE_USE_PREFIX + 'rep')`.
Returns: Returns:
self self
@ -810,8 +818,7 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta):
if name_func is None: if name_func is None:
def name_func(_pat, _shape): def name_func(_pat, _shape):
return self.get_name('_rep') return self.get_name(SINGLE_USE_PREFIX = 'rep')
#name_func = lambda _pat, _shape: self.get_name('_rep')
for pat in tuple(self.values()): for pat in tuple(self.values()):
for layer in pat.shapes: for layer in pat.shapes: