Compare commits

...

4 Commits

Author SHA1 Message Date
c115780bc7 bump version to v3.1 2024-03-30 18:02:40 -07:00
66d9a4eff8 add note about github mirror 2024-03-30 18:01:14 -07:00
3a0c49174b improve variable naming 2024-03-30 18:01:14 -07:00
8d122cbd2e add path_into() 2024-03-30 18:01:08 -07:00
4 changed files with 94 additions and 5 deletions

View File

@ -8,6 +8,7 @@ to output to multiple formats.
- [Source repository](https://mpxd.net/code/jan/masque) - [Source repository](https://mpxd.net/code/jan/masque)
- [PyPI](https://pypi.org/project/masque) - [PyPI](https://pypi.org/project/masque)
- [Github mirror](https://github.com/anewusername/masque)
## Installation ## Installation

View File

@ -48,5 +48,5 @@ from .utils import ports2data, oneshot
__author__ = 'Jan Petykiewicz' __author__ = 'Jan Petykiewicz'
__version__ = '3.0' __version__ = '3.1'
version = __version__ # legacy version = __version__ # legacy

View File

@ -15,7 +15,7 @@ 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
from ..utils import SupportsBool from ..utils import SupportsBool, rotation_matrix_2d
from .tools import Tool from .tools import Tool
from .utils import ell from .utils import ell
from .builder import Builder from .builder import Builder
@ -431,6 +431,92 @@ class Pather(Builder):
) )
def path_into(
self,
portspec_src: str,
portspec_dst: str,
*,
tool_port_names: tuple[str, str] = ('A', 'B'),
out_ptype: str = 'unk',
plug_destination: bool = True,
**kwargs,
) -> Self:
if self._dead:
logger.error('Skipping path_into() since device is dead')
return self
port_src = self.pattern[portspec_src]
port_dst = self.pattern[portspec_dst]
if port_src.rotation is None:
raise PortError(f'Port {portspec_src} has no rotation and cannot be used for path_into()')
if port_dst.rotation is None:
raise PortError(f'Port {portspec_dst} has no rotation and cannot be used for path_into()')
if not numpy.isclose(port_src.rotation % (pi / 2), 0):
raise BuildError('path_to was asked to route from non-manhattan port')
if not numpy.isclose(port_dst.rotation % (pi / 2), 0):
raise BuildError('path_to was asked to route to non-manhattan port')
src_is_horizontal = numpy.isclose(port_src.rotation % pi, 0)
dst_is_horizontal = numpy.isclose(port_dst.rotation % pi, 0)
xs, ys = port_src.offset
xd, yd = port_dst.offset
angle = (port_dst.rotation - port_src.rotation) % (2 * pi)
src_ne = port_src.rotation % (2 * pi) > (3 * pi /4) # path from src will go north or east
def get_jog(ccw: SupportsBool, length: float) -> float:
tool = self.tools.get(portspec_src, self.tools[None])
in_ptype = 'unk' # Could use port_src.ptype, but we're assuming this is after one bend already...
tree2 = tool.path(ccw, length, in_ptype=in_ptype, port_names=('A', 'B'), out_ptype=out_ptype, **kwargs)
top2 = tree2.top_pattern()
jog = rotation_matrix_2d(top2['A'].rotation) @ (top2['B'].offset - top2['A'].offset)
return jog[1]
dst_extra_args = {'out_ptype': out_ptype}
if plug_destination:
dst_extra_args['plug_into'] = portspec_dst
src_args = {**kwargs, 'tool_port_names': tool_port_names}
dst_args = {**src_args, **dst_extra_args}
if src_is_horizontal and not dst_is_horizontal:
# single bend should suffice
self.path_to(portspec_src, angle > pi, x=xd, **src_args)
self.path_to(portspec_src, None, y=yd, **dst_args)
elif dst_is_horizontal and not src_is_horizontal:
# single bend should suffice
self.path_to(portspec_src, angle > pi, y=yd, **src_args)
self.path_to(portspec_src, None, x=xd, **dst_args)
elif numpy.isclose(angle, pi):
if src_is_horizontal and ys == yd:
# straight connector
self.path_to(portspec_src, None, x=xd, **dst_args)
elif not src_is_horizontal and xs == xd:
# straight connector
self.path_to(portspec_src, None, y=yd, **dst_args)
elif src_is_horizontal:
# figure out how much x our y-segment (2nd) takes up, then path based on that
y_len = numpy.abs(yd - ys)
ccw2 = src_ne != (yd > ys)
jog = get_jog(ccw2, y_len) * numpy.sign(xd - xs)
self.path_to(portspec_src, not ccw2, x=xd - jog, **src_args)
self.path_to(portspec_src, ccw2, y=yd, **dst_args)
else:
# figure out how much y our x-segment (2nd) takes up, then path based on that
x_len = numpy.abs(xd - xs)
ccw2 = src_ne != (xd < xs)
jog = get_jog(ccw2, x_len) * numpy.sign(yd - ys)
self.path_to(portspec_src, not ccw2, y=yd - jog, **src_args)
self.path_to(portspec_src, ccw2, x=xd, **dst_args)
elif numpy.isclose(angle, 0):
raise BuildError(f'Don\'t know how to route a U-bend at this time!')
else:
raise BuildError(f'Don\'t know how to route ports with relative angle {angle}')
return self
def mpath( def mpath(
self, self,
portspec: str | Sequence[str], portspec: str | Sequence[str],

View File

@ -62,7 +62,8 @@ def maxrects_bssf(
''' Place the rect ''' ''' Place the rect '''
# Best short-side fit (bssf) to pick a region # Best short-side fit (bssf) to pick a region
bssf_scores = ((regions[:, 2:] - regions[:, :2]) - rect_size).min(axis=1).astype(float) region_sizes = regions[:, 2:] - regions[:, :2]
bssf_scores = (region_sizes - rect_size).min(axis=1).astype(float)
bssf_scores[bssf_scores < 0] = numpy.inf # doesn't fit! bssf_scores[bssf_scores < 0] = numpy.inf # doesn't fit!
rr = bssf_scores.argmin() rr = bssf_scores.argmin()
if numpy.isinf(bssf_scores[rr]): if numpy.isinf(bssf_scores[rr]):
@ -152,7 +153,8 @@ def guillotine_bssf_sas(
for rect_ind, rect_size in enumerate(rect_sizes): for rect_ind, rect_size in enumerate(rect_sizes):
''' Place the rect ''' ''' Place the rect '''
# Best short-side fit (bssf) to pick a region # Best short-side fit (bssf) to pick a region
bssf_scores = ((regions[:, 2:] - regions[:, :2]) - rect_size).min(axis=1).astype(float) region_sizes = regions[:, 2:] - regions[:, :2]
bssf_scores = (region_sizes - rect_size).min(axis=1).astype(float)
bssf_scores[bssf_scores < 0] = numpy.inf # doesn't fit! bssf_scores[bssf_scores < 0] = numpy.inf # doesn't fit!
rr = bssf_scores.argmin() rr = bssf_scores.argmin()
if numpy.isinf(bssf_scores[rr]): if numpy.isinf(bssf_scores[rr]):
@ -166,7 +168,7 @@ def guillotine_bssf_sas(
loc = regions[rr, :2] loc = regions[rr, :2]
rect_locs[rect_ind] = loc rect_locs[rect_ind] = loc
region_size = regions[rr, 2:] - loc region_size = region_sizes[rr]
split_horiz = region_size[0] < region_size[1] split_horiz = region_size[0] < region_size[1]
new_region0 = regions[rr].copy() new_region0 = regions[rr].copy()