def end_y_settable(self): spanner = MockSpanner2D( Point(Unit(0), Unit(1)), None, Point(Unit(2), Unit(3)), None ) assert spanner.end_pos == Point(Unit(2), Unit(3)) spanner.end_y = Unit(10) assert spanner.end_pos == Point(Unit(2), Unit(10))
def test_shape(self): beam = Beam( (Mm(1), Mm(2)), self.left_parent, (Mm(3), Mm(4)), self.right_parent, ) thickness = beam.beam_thickness assert len(beam.elements) == 5 assert_path_els_equal(beam.elements[0], MoveTo(Point(Mm(0), Mm(0)), beam)) assert_path_els_equal(beam.elements[1], LineTo(Point(Mm(3), Mm(4)), self.right_parent)) assert_path_els_equal( beam.elements[2], LineTo( Point(Mm(3), Mm(4) + thickness), self.right_parent, ), ) assert_path_els_equal( beam.elements[3], LineTo( Point(Mm(0), thickness), beam, ), ) assert_path_els_equal( beam.elements[4], MoveTo( Point(Mm(0), Mm(0)), beam, ), )
def _render_in_flowable(self): """Render the object to the scene, dispatching partial rendering calls when needed if an object flows across a break in the flowable. Returns: None """ # Calculate position within flowable pos_in_flowable = descendant_pos(self, self.flowable) remaining_x = self.length + self.flowable.dist_to_line_end( pos_in_flowable.x) if remaining_x < ZERO: self._render_complete( canvas_pos_of(self), self.flowable.dist_to_line_start(pos_in_flowable.x), pos_in_flowable.x, ) return # Render before break first_line_i = self.flowable.last_break_index_at(pos_in_flowable.x) current_line = self.flowable.layout_controllers[first_line_i] render_start_pos = canvas_pos_of(self) first_line_length = self.flowable.dist_to_line_end( pos_in_flowable.x) * -1 render_end_pos = Point(render_start_pos.x + first_line_length, render_start_pos.y) self._render_before_break( pos_in_flowable.x, render_start_pos, render_end_pos, self.flowable.dist_to_line_start(pos_in_flowable.x), ) # Iterate through remaining length for current_line_i in range(first_line_i + 1, len(self.flowable.layout_controllers)): current_line = self.flowable.layout_controllers[current_line_i] if remaining_x > current_line.length: # Render spanning continuation line_pos = canvas_pos_of(current_line) render_start_pos = Point(line_pos.x, line_pos.y + pos_in_flowable.y) render_end_pos = Point( render_start_pos.x + current_line.length, render_start_pos.y) self._render_spanning_continuation(self.length - remaining_x, render_start_pos, render_end_pos) remaining_x -= current_line.length else: break # Render end render_start_pos = self.flowable.map_to_canvas( Point(current_line.flowable_x, pos_in_flowable.y)) render_end_pos = Point(render_start_pos.x + remaining_x, render_start_pos.y) self._render_after_break(self.length - remaining_x, render_start_pos, render_end_pos)
def test_hairpin_points_diagonal_different_parents(self): # For reference... # self.left_parent = MockStaffObject((Unit(0), Unit(0)), self.staff) # self.right_parent = MockStaffObject((Unit(10), Unit(2)), self.staff) cresc = Hairpin( (Unit(10), Unit(2)), self.left_parent, (Unit(4), Unit(4)), self.right_parent, 1, Unit(2), ) # Spanner line slope should be Unit(1) points = cresc._find_hairpin_points() assert_almost_equal(points[0].x, points[4].y) assert_almost_equal(points[0].y, points[4].x) assert points[2] == Point(Unit(10), Unit(2)) assert points[3] == self.left_parent dim = Hairpin( (Unit(0), Unit(0)), self.left_parent, (Unit(-6), Unit(2)), self.right_parent, -1, Unit(2), ) # Spanner line slope should be Unit(1) points = dim._find_hairpin_points() assert_almost_equal(points[0].x, points[4].y) assert_almost_equal(points[0].y, points[4].x) assert points[2] == Point(Unit(-6), Unit(2)) assert points[3] == self.right_parent
def test_length_with_parents(self): parent_1 = InvisibleObject((Unit(1), Unit(2)), None) parent_2 = InvisibleObject((Unit(11), Unit(12)), None) spanner = MockSpanner2D( Point(Unit(1), Unit(2)), parent_1, Point(Unit(4), Unit(5)), parent_2 ) # math.sqrt(((15-2)**2) + ((17-4)**2)) assert_almost_equal(spanner.spanner_2d_length, Unit(18.384776310850235))
def test__eq__(self): p1 = Point(Unit(5), Unit(6)) p2 = Point(Unit(5), Unit(6)) p3 = Point(Unit(5), Unit(1234)) p4 = Point(Unit(1234), Unit(6)) assert p1 == p2 assert p1 != p3 assert p1 != p4
def test_length_no_parents(self): spanner = MockSpanner2D( Point(Unit(1), Unit(2)), neoscore.document.pages[0], Point(Unit(5), Unit(7)), neoscore.document.pages[0], ) # math.sqrt(((5-1)**2) + ((7-2)**2)) assert_almost_equal(spanner.spanner_2d_length, Unit(6.4031242374328485))
def test_length_with_self_parent(self): parent = MockSpanner2D( Point(Unit(1), Unit(2)), None, Point(Unit(0), Unit(0)), None ) spanner = MockSpanner2D( Point(Unit(3), Unit(7)), parent, Point(Unit(4), Unit(5)), None ) # math.sqrt((4**2) + (5**2)) assert_almost_equal(spanner.spanner_2d_length, Unit(6.4031242374328485))
def test_rhythm_dot_positions_with_rest(self): chord = Chordrest(Mm(1), self.staff, None, Beat(7, 16)) dots = list(chord.rhythm_dot_positions) dots.sort(key=lambda d: d.x) assert_almost_equal( dots[0], Point(self.staff.unit(1.326), self.staff.unit(1.5)) ) assert_almost_equal( dots[1], Point(self.staff.unit(1.826), self.staff.unit(1.5)) )
def test_map_between(self): source = InvisibleObject((Unit(5), Unit(6)), neoscore.document.pages[1]) destination = InvisibleObject((Unit(99), Unit(90)), neoscore.document.pages[4]) relative_pos = map_between(source, destination) page_1_pos = canvas_pos_of(neoscore.document.pages[1]) page_4_pos = canvas_pos_of(neoscore.document.pages[4]) expected = (page_4_pos + Point(Unit(99), Unit(90))) - ( page_1_pos + Point(Unit(5), Unit(6))) assert_almost_equal(relative_pos, expected)
def _generate_layout_controllers(self) -> list[NewLine]: """Generate automatic layout controllers. The generated controllers are stored in `self.layout_controllers` in sorted order according to ascending x position """ live_page_width = neoscore.document.paper.live_width live_page_height = neoscore.document.paper.live_height # local progress of layout generation; when the entire flowable has # been covered, this will be equal to `self.width` x_progress = ZERO # Current position on the page relative to the top left corner # of the live page area pos_x = self.pos.x pos_y = self.pos.y current_page = 0 # Attach initial line controller layout_controllers = [ NewLine(self.pos, neoscore.document.pages[current_page], x_progress, self.height) ] while True: x_progress += live_page_width - pos_x pos_y = pos_y + self.height + self.y_padding if x_progress >= self.length: # End of breakable width - Done. break if pos_y > live_page_height: # Page break - No y offset pos_x = ZERO pos_y = ZERO current_page += 1 layout_controllers.append( NewLine( Point(pos_x, pos_y), neoscore.document.pages[current_page], x_progress, self.height, )) else: # Line break - self.y_padding as y offset pos_x = ZERO layout_controllers.append( NewLine( Point(pos_x, pos_y), neoscore.document.pages[current_page], x_progress, self.height, self.y_padding, )) return layout_controllers
def __init__( self, start: PointDef, start_parent: GraphicObject, end: PointDef, end_parent: Optional[GraphicObject] = None, ): """ Args: start: The position of the start-pedal mark relative to `start_parent`. start_parent: Anchor for the start-pedal mark, which must be in a staff or a staff itself. end: The position of the release-pedal mark relative to `end_parent`. end_parent: An optional anchor for the release-pedal mark. If provided, this must be in the same staff as `start_parent`. Otherwise, this defaults to `self`. """ ObjectGroup.__init__(self, start, start_parent) Spanner2D.__init__( self, end if isinstance(end, Point) else Point(*end), cast(Positioned, end_parent) if end_parent else self, ) StaffObject.__init__(self, self.parent) # Add opening pedal mark # (GraphicObject init handles registration with ObjectGroup) self.depress_mark = MusicText((GraphicUnit(0), GraphicUnit(0)), "keyboardPedalPed", parent=self) self.lift_mark = MusicText(self.end_pos, "keyboardPedalUp", parent=self.end_parent)
def test_map_between_where_src_parent_is_dest(self): destination = InvisibleObject((Unit(3), Unit(10)), neoscore.document.pages[0]) source = InvisibleObject((Unit(1), Unit(2)), destination) relative_pos = map_between(source, destination) expected = Point(Unit(-1), Unit(-2)) assert_almost_equal(relative_pos, expected)
def __init__( self, start: PointDef, start_parent: GraphicObject, stop: PointDef, stop_parent: Optional[GraphicObject], direction: int, width: Optional[Unit] = None, ): """ Args: start: The starting point. start_parent: The parent for the starting position. Must be a staff or in one. stop: The stopping point. stop_parent: The parent for the ending position. If `None`, defaults to `self`. direction: The direction of the hairpin, where `-1` means diminuendo (>) and `1` means crescendo (<). width: The width of the wide hairpin. Defaults to 1 staff unit. """ Path.__init__(self, start, parent=start_parent) StaffObject.__init__(self, start_parent) stop = Point.from_def(stop) Spanner2D.__init__(self, stop, stop_parent or self) self.direction = direction self.width = width if width is not None else self.staff.unit(1) self.thickness = self.staff.music_font.engraving_defaults[ "hairpinThickness"] self._draw_path()
def _render_occurrence(self, pos: Point, local_start_x: Unit, shift_for_clef: bool): """Render one appearance of one key signature accidental. Much of the positioning code needs to be performed per-occurrence because key signatures can have different appearances when clefs change. Ideally there should be a way to cache/centralize much of this work. """ staff_pos_in_flowable = map_between(self.flowable, self.staff) pos_x_in_staff = local_start_x - staff_pos_in_flowable.x clef = self.staff.active_clef_at(pos_x_in_staff) if clef is None: return clef_type = clef.clef_type pos_tuple = _KeySignatureAccidental.positions[ self.accidental_type][clef_type][self.pitch_letter] visual_pos_x = self.staff.unit(pos_tuple[0]) + pos.x visual_pos_y = self.staff.unit(pos_tuple[1]) + pos.y if shift_for_clef: visual_pos_x += self._padded_clef_width(clef) self._render_slice(Point(visual_pos_x, visual_pos_y))
def __init__( self, start: PointDef, start_parent: GraphicObject, stop: PointDef, stop_parent: GraphicObject, ): """ Args: start: The starting (left) position of the beam start_parent: The parent for the starting position. Must be a staff or in one. stop: The ending (right) position of the beam stop_parent: The parent for the ending position. Must be a staff or in one. """ Path.__init__(self, start, parent=start_parent) StaffObject.__init__(self, start_parent) self.beam_thickness = self.staff.music_font.engraving_defaults[ "beamThickness"] # Draw beam stop = Point.from_def(stop) self.line_to(stop.x, stop.y, stop_parent) self.line_to(stop.x, stop.y + self.beam_thickness, stop_parent) self.line_to(ZERO, self.beam_thickness, self) self.close_subpath()
def __init__( self, start: PointDef, start_parent: GraphicObject, stop: PointDef, stop_parent: Optional[GraphicObject], direction: int = -1, ): """ Args: start: The starting point. start_parent: The parent for the starting position. Must be a staff or in one. stop: The stopping point. stop_parent: The parent for the ending position. If `None`, defaults to `self`. direction: The direction of the slur, where `-1` indicates curving upward, and `1` vice versa. """ Path.__init__(self, start, parent=start_parent, brush=Brush((0, 0, 0, 255))) StaffObject.__init__(self, self.parent) stop = Point.from_def(stop) Spanner2D.__init__(self, stop, stop_parent or self) self.direction = direction # Load relevant engraving defaults from music font engraving_defaults = self.staff.music_font.engraving_defaults self.midpoint_thickness = self.staff.unit( engraving_defaults["slurMidpointThickness"]) self.endpoint_thickness = self.staff.unit( engraving_defaults["slurEndpointThickness"]) self._draw_path()
def test_page_origin_at_first_page(self): left_margin = Mm(13) top_margin = Mm(21) test_paper = Paper(Mm(200), Mm(250), top_margin, Mm(10), Mm(20), left_margin, Mm(0)) test_doc = Document(test_paper) found = test_doc.page_origin(0) assert_almost_equal(found, Point(left_margin, top_margin))
def test_move_to_with_parent(self): path = Path(ORIGIN) parent = InvisibleObject((Unit(100), Unit(50))) path.move_to(Unit(10), Unit(11), parent) assert len(path.elements) == 1 assert_path_els_equal( path.elements[0], MoveTo(Point(Unit(10), Unit(11)), parent) )
def test_init(self): mock_parent = InvisibleObject(ORIGIN, parent=None) test_pen = Pen("#eeeeee") test_brush = Brush("#dddddd") path = Path((Unit(5), Unit(6)), test_pen, test_brush, mock_parent) assert path.pos == Point(Unit(5), Unit(6)) assert path.pen == test_pen assert path.brush == test_brush
def test_init(self): test_flowable = Flowable((Mm(10), Mm(11)), Mm(1000), Mm(100), Mm(5)) assert test_flowable.pos == Point(Mm(10), Mm(11)) assert test_flowable.x == Mm(10) assert test_flowable.y == Mm(11) assert test_flowable.length == Mm(1000) assert test_flowable.height == Mm(100) assert test_flowable.y_padding == Mm(5)
def test_generate_layout_controllers_with_only_one_line(self): test_flowable = Flowable((Mm(9), Mm(11)), Mm(100), Mm(50), Mm(5)) test_flowable._generate_layout_controllers() assert len(test_flowable.layout_controllers) == 1 assert test_flowable.layout_controllers[0].flowable_x == Mm(0) assert test_flowable.layout_controllers[0].pos == Point(Mm(9), Mm(11)) assert test_flowable.layout_controllers[ 0].page == neoscore.document.pages[0]
def test_map_between_with_common_parent(self): parent = InvisibleObject((Unit(5), Unit(6)), neoscore.document.pages[0]) source = InvisibleObject((Unit(1), Unit(2)), parent) destination = InvisibleObject((Unit(3), Unit(10)), parent) relative_pos = map_between(source, destination) expected = Point(Unit(2), Unit(8)) assert_almost_equal(relative_pos, expected)
def qt_point_to_point(qt_point: Union[QPoint, QPointF]) -> Point: """Create a Point from a QPoint or QPointF Args: qt_point: The source point Returns: Point """ return Point(GraphicUnit(qt_point.x()), GraphicUnit(qt_point.y()))
def cubic_to( self, control_1_x: Unit, control_1_y: Unit, control_2_x: Unit, control_2_y: Unit, end_x: Unit, end_y: Unit, control_1_parent: Optional[Parent] = None, control_2_parent: Optional[Parent] = None, end_parent: Optional[Parent] = None, ): """Draw a cubic bezier curve from the current position to a new point. If the path is empty, this will add two elements, an initial `MoveTo(Point(Unit(0), Unit(0)), self)` and the requested `CurveTo`. Args: control_1_x: The x coordinate of the first control point. control_1_y: The y coordinate of the first control point. control_2_x: The x coordinate of the second control point. control_2_y: The y coordinate of the second control point. end_x: The x coordinate of the curve target. end_y: The y coordinate of the curve target. control_1_parent: An optional parent for the first control point. Defaults to `self`. control_2_parent: An optional parent for the second control point. Defaults to `self`. end_parent: An optional parent for the curve target. Defaults to `self`. """ c1 = ControlPoint( Point(control_1_x, control_1_y), control_1_parent or self, ) c2 = ControlPoint( Point(control_2_x, control_2_y), control_2_parent or self, ) if not len(self.elements): # Needed to ensure bounding rect / length calculations are correct self.elements.append(MoveTo(Point(Unit(0), Unit(0)), self)) self.elements.append(CurveTo(c1, c2, Point(end_x, end_y), end_parent or self))
def test_cubic_to_with_no_parents(self): path = Path((Unit(5), Unit(6))) path.cubic_to(Unit(10), Unit(11), ZERO, Unit(1), Unit(5), Unit(6)) assert len(path.elements) == 2 assert_path_els_equal(path.elements[0], MoveTo(ORIGIN, path)) assert_path_els_equal( path.elements[1], CurveTo( ControlPoint(Point(Unit(10), Unit(11)), path), ControlPoint(Point(ZERO, Unit(1)), path), Point(Unit(5), Unit(6)), path, ), ) resolved_els = path._resolve_path_elements() assert resolved_els == [ ResolvedMoveTo(ZERO, ZERO), ResolvedCurveTo(Unit(10), Unit(11), ZERO, Unit(1), Unit(5), Unit(6)), ]
def _create_stem(self): """Create a Stem and stores it in `self.stem`. Returns: None """ self._stem = Stem( Point(self.staff.unit(0), self.furthest_notehead.staff_pos), self.stem_height, self, )
def map_to_canvas(self, local_point: Point) -> Point: """Convert a local point to its position in the canvas. Args: local_point: A position in the flowable's local space. """ line = self.last_break_at(local_point.x) line_canvas_pos = canvas_pos_of(line) return line_canvas_pos + Point(local_point.x - line.flowable_x, local_point.y)
def test_line_to(self): path = Path((Unit(5), Unit(6))) path.line_to(Unit(10), Unit(12)) assert len(path.elements) == 2 assert_path_els_equal(path.elements[0], MoveTo(ORIGIN, path)) assert_path_els_equal(path.elements[1], LineTo(Point(Unit(10), Unit(12)), path)) resolved_els = path._resolve_path_elements() assert resolved_els == [ ResolvedMoveTo(ZERO, ZERO), ResolvedLineTo(Unit(10), Unit(12)), ]
def line_to(self, x: Unit, y: Unit, parent: Optional[Parent] = None): """Draw a path from the current position to a new point. A point parent may be passed as well, anchored the target point to a separate GraphicObject. In this case, the coordinates passed will be considered relative to the parent. If the path is empty, this will add two elements, an initial `MoveTo(Point(Unit(0), Unit(0)), self)` and the requested `LineTo`. Args: x: The end x position y: The end y position parent: An optional parent, whose position the target coordinate will be relative to. """ if not len(self.elements): # Needed to ensure bounding rect / length calculations are correct self.elements.append(MoveTo(Point(Unit(0), Unit(0)), self)) self.elements.append(LineTo(Point(x, y), parent or self))