def point_position(self, in_anchor, offset, out_anchor=None): out_anchor = out_anchor or self.state if in_anchor == "in" and out_anchor == "out": return Position(offset, 0, 0) if in_anchor == "in" and out_anchor == "branch": t = self.intermediate_branch_t[max( 0, min(int(offset / self.branch_length * 100), 99))] x, y = self.branch_bezier(t) x1, y1 = self.branch_bezier(t - 1e-6) x2, y2 = self.branch_bezier(t + 1e-6) theta = math.atan2(y2 - y1, x2 - x1) return Position(x, y, theta) if in_anchor == "branch": t = self.intermediate_branch_t[max( 0, min(99 - int(offset / self.branch_length * 100), 99))] x, y = self.branch_bezier(t) x1, y1 = self.branch_bezier(t - 1e-6) x2, y2 = self.branch_bezier(t + 1e-6) theta = math.atan2(y2 - y1, x2 - x1) return Position(x, y, theta) if in_anchor == "out": return Position(32 - offset, 0, math.pi)
def relative_positions(self): return { **super().relative_positions(), "out": Position(self.length, 0, 0), "left": Position(self.length / 2, -self.length / 2, -math.pi / 2), "right": Position(self.length / 2, self.length / 2, math.pi / 2), }
def relative_positions(self): return { **super().relative_positions(), "out": Position(32, 0, 0), "branch": Position(*self.branch_point, math.tau / 16 * self.coordinate_sign), }
def point_position(self, in_anchor, offset, out_anchor=None) -> Position: if in_anchor == "in": return Position(offset, 0, 0) elif in_anchor == "out": return Position(self.length - offset, 0, math.pi) elif in_anchor == "left": return Position(self.length / 2, self.length / 2 - offset, -math.pi / 2) elif in_anchor == "right": return Position(self.length / 2, offset - self.length / 2, math.pi / 2) else: raise AssertionError
def test_anchor_position_bookkeeping(self): layout = Layout() drawing_area = unittest.mock.Mock() layout_drawer = LayoutDrawer(drawing_area, layout) piece_1 = Straight(layout=layout, placement=Position(0, 0, 0)) layout.add_piece(piece_1) piece_2 = Straight(layout=layout) layout.add_piece(piece_2) piece_3 = Straight(layout=layout) layout.add_piece(piece_3) # Connect them explicitly not out->in, out->in, and check how many are positioned in the layout as we go self.assertEqual(2, len(layout_drawer.layout.anchors_qtree)) piece_1.anchors["out"] += piece_2.anchors["in"] self.assertEqual(3, len(layout_drawer.layout.anchors_qtree)) piece_3.anchors["in"] += piece_2.anchors["out"] # for anchor, bbox in layout_drawer.anchors_qtree._bounds.items(): # print(f' {bbox} - {anchor}') self.assertEqual(4, len(layout_drawer.layout.anchors_qtree)) layout.remove_piece(piece_2) self.assertEqual(4, len(layout_drawer.layout.anchors_qtree)) layout.remove_piece(piece_1) self.assertEqual(2, len(layout_drawer.layout.anchors_qtree)) layout.remove_piece(piece_3) self.assertEqual(0, len(layout_drawer.layout.anchors_qtree))
def on_piece_positioned(self, sender: Piece): if self.track_point.position: position = self.track_point.position + Position(0, 5, 0) else: position = None if position != self._position: self._position = position signals.sensor_positioned.send(self)
def place_piece(self, piece_cls: Type[Piece], x: float, y: float): possible_anchors = self.layout.anchors_qtree.intersect( (x - 8, y - 8, x + 8, y + 8) ) possible_anchors = [anchor for anchor in possible_anchors if len(anchor) < 2] if possible_anchors: piece = piece_cls(layout=self.layout) possible_anchors[0] += piece.anchors[piece.anchor_names[0]] else: # Snap to an 8x8 grid x = 8 * ((x + 4) // 8) y = 8 * ((y + 4) // 8) piece = piece_cls(layout=self.layout, placement=Position(x, y, angle=0)) self.connect_coincident_anchors(piece) self.layout.add_piece(piece)
def relative_positions(self) -> Dict[str, Position]: """Returns a mapping from anchor name to that anchor's relative position. The base implementation provides a relative position for the first anchor, which should always be backwards out from the piece position (i.e. x=0, y=0, theta=pi). Subclasses should override and extend this method to provide relative positions for other anchors. e.g., for a hypothetical 90° 40R curve piece: >>> def relative_positions(self): >>> return { >>> **super().relative_positions(), >>> 'out': Position(40, -40, math.pi / 2), >>> } This method is used when calculating positions for connected pieces, starting from the placement origin (i.e. the piece in a connected subset which has `self.placement` set). """ return {self.anchor_names[0]: Position(0, 0, math.pi)}
piece_cls = self.piece_mapping[segment_data["type"]] node_count = int(segment_data["nodes"]) assert len(piece_cls.anchor_names) == node_count node_ids = [ int(segment_data[f"node{i}"]) for i in range(1, node_count + 1) ] # if segment_data['type'] in self.anchor_name_mapping: # node_ids = [ # node_ids[piece_cls.anchor_names.index(anchor_name)] # for anchor_name in self.anchor_name_mapping[segment_data['type']] # ] piece_nodes = [nodes[node_id] for node_id in node_ids] placement = Position( piece_nodes[0]["x"], piece_nodes[0]["y"], float(segment_data["angle"]) / 360 * math.tau, ) piece = piece_cls( layout=layout, id=segment.find("index").attrib["value"], placement=placement, **self.piece_params.get(segment_data["type"], {}), ) layout.add_piece(piece, announce=False) for node, anchor_name in zip(piece_nodes, piece.anchor_names): if node["anchor"]: node["anchor"] += piece.anchors[anchor_name] else: node["anchor"] = piece.anchors[anchor_name]