gcode_reader.emulate.motion =========================== .. py:module:: gcode_reader.emulate.motion Classes ------- .. autoapisummary:: gcode_reader.emulate.motion.ConstantVelocity gcode_reader.emulate.motion.LinearRamp gcode_reader.emulate.motion.MotionProfile gcode_reader.emulate.motion.MotionSegment gcode_reader.emulate.motion.SCurve gcode_reader.emulate.motion.Trapezoid Functions --------- .. autoapisummary:: gcode_reader.emulate.motion.extract_motion_scalars Module Contents --------------- .. py:class:: ConstantVelocity(initial_position: tuple, final_position: tuple, target_velocity: float, **_) Bases: :py:obj:`MotionSegment` Constant Velocity motion profile .. py:method:: acceleration(t: float) Instantaneous acceleration at a time (t) on the line segment initial_position, final_position .. py:method:: calculate() .. py:method:: position(t) Instantaneous position at a time (t) on the line segment initial_position, final_position .. py:method:: velocity(t: float) Instantaneous velocity at a time (t) on the line segment initial_position, final_position .. py:property:: elapsed_time Elapsed time required to travel the line segment initial_position, final_position .. py:attribute:: v .. py:class:: LinearRamp(initial_position: tuple, final_position: tuple, initial_velocity: float, final_velocity: float, max_acceleration: float = None, _distance: float = None, **_) Bases: :py:obj:`MotionSegment` Linear Velocity motion profile .. py:method:: acceleration(t: float) Instantaneous acceleration at a time (t) on the line segment initial_position, final_position .. py:method:: calculate() .. py:method:: position(t) Instantaneous position at a time (t) on the line segment initial_position, final_position .. py:method:: velocity(t: float) Instantaneous velocity at a time (t) on the line segment initial_position, final_position .. py:property:: elapsed_time Elapsed time required to travel the line segment initial_position, final_position .. py:attribute:: final_velocity .. py:attribute:: initial_velocity .. py:attribute:: max_acceleration :value: None .. py:class:: MotionProfile(max_jerk: float = None, max_accleration: float = None, max_velocity: float = None, junction_deviation: float = 0.05) Factory for generating `MotionSegment` objects from process data. Holds machine-wide kinematic constraints and orchestrates a two-pass (GRBL-style) velocity planning algorithm: junction-deviation corner limiting followed by forward and backward kinematic propagation. :param max_jerk: Maximum allowed jerk (units/s³). Used only by SCurve segments. Defaults to None. :type max_jerk: float, optional :param max_accleration: Maximum allowed acceleration (units/s²). Defaults to None. :type max_accleration: float, optional :param max_velocity: Ceiling on all target velocities (units/s). Defaults to None. :type max_velocity: float, optional :param junction_deviation: Junction-deviation parameter (same length units as positions). Larger values permit higher corner velocities. Defaults to 0.05. :type junction_deviation: float, optional .. attribute:: feed_rate_time_base_s Divisor applied to feed-rate values to convert them from units/minute to units/second. Defaults to 60.0. :type: float .. py:method:: from_operation(operation: gcode_reader.emulate.operations.Operation, home=(0, 0, 0)) Convenience wrapper around ``from_process_data`` that attaches the resulting profile directly to an ``Operation``. Mutates ``operation`` in place: sets ``operation.motion_profile`` to the list of segments and writes ``elapsed_time`` back onto each corresponding ``ProcessData`` entry. Returns the same object so the call can be chained. Example:: planner = MotionProfile(max_accleration=500.0, max_velocity=50.0) operation = planner.from_operation(operation) total_time = sum( pd.elapsed_time for pd in operation.process_data ) :param operation: The ``Operation`` to profile. Mutated in place. :param home: Machine position before the first move. Defaults to (0, 0, 0). :returns: The same ``Operation``, with ``motion_profile`` and ``elapsed_time`` populated. .. py:method:: from_process_data(process_data, home=(0, 0, 0)) Convert process data into a timed motion profile. Accepts either a ``List[ProcessData]`` or a ``ProcessDataArrays``. Zero-length moves (duplicate waypoints) yield ``None``. Example:: planner = MotionProfile(max_accleration=500.0, max_velocity=50.0) segments = list(planner.from_process_data(operation.process_data)) segments = [s for s in segments if s is not None] total_time = sum(s.elapsed_time for s in segments) :param process_data: Move data as a list of ``ProcessData`` objects or a pre-built ``ProcessDataArrays``. :param home: Machine position before the first move. Defaults to (0, 0, 0). :Yields: A ``MotionSegment`` for each move, or ``None`` for zero-length moves. .. py:attribute:: feed_rate_time_base_s :value: 60.0 .. py:attribute:: junction_deviation :value: 0.05 .. py:attribute:: max_acceleration :value: None .. py:attribute:: max_jerk :value: None .. py:attribute:: max_velocity :value: None .. py:property:: profile_type .. py:class:: MotionSegment(initial_position: tuple, final_position: tuple, _distance: float = None, **_) Base class for all motion profiles :param initial_position: Initial position :type initial_position: tuple :param final_position: Final position :type final_position: tuple :param _distance: Precomputed distance to skip np.linalg.norm. :type _distance: float, optional .. py:method:: acceleration(t: float) Instantaneous acceleration at a time (t) on the line segment initial_position, final_position .. py:method:: calculate() .. py:method:: jerk(t: float) Instantaneous jerk (da/dt) at a time (t) on the line segment initial_position, final_position .. py:method:: position(t: float) Instantaneous position at a time (t) on the line segment initial_position, final_position .. py:method:: velocity(t: float) Instantaneous velocity at a time (t) on the line segment initial_position, final_position .. py:attribute:: direction .. py:attribute:: distance :value: None .. py:property:: elapsed_time Elapsed time required to travel the line segment initial_position, final_position .. py:attribute:: final_position .. py:attribute:: initial_position .. py:class:: SCurve(initial_position: tuple, final_position: tuple, initial_velocity: float, target_velocity: float, final_velocity: float = None, max_acceleration: float = None, max_jerk: float = None, **_) Bases: :py:obj:`MotionSegment` Models an S-curve motion profile for a linear segment. The acceleration profile is trapezoidal (rather than step-wise), giving continuous acceleration and bounded jerk. The profile has up to seven phases: three for ramp-up, a constant-velocity cruise, and three for ramp-down. The profile operates in one of two modes: - **Distance-Limited:** If `max_jerk` is not provided, ramp-up and ramp-down each occupy one-third of the total move distance, and the required accelerations are derived from that constraint. - **Jerk-Limited:** If `max_jerk` is provided, it governs the acceleration shape. If `max_acceleration` is also supplied it acts as a ceiling on the jerk-derived acceleration. The `target_velocity` is reduced automatically when the move is too short for the requested velocity (triangular case). :param initial_position: The starting coordinate of the move. :type initial_position: tuple :param final_position: The ending coordinate of the move. :type final_position: tuple :param initial_velocity: The velocity at the start of the move. :type initial_velocity: float :param target_velocity: The target cruising velocity. :type target_velocity: float :param final_velocity: The velocity at the end of the move. If None, defaults to `initial_velocity`. :type final_velocity: float, optional :param max_acceleration: Acceleration ceiling used only in jerk-limited mode. Ignored in distance-limited mode. :type max_acceleration: float, optional :param max_jerk: Maximum jerk. If provided the profile is jerk-limited; if None it is distance-limited. :type max_jerk: float, optional .. py:method:: acceleration(t) Instantaneous acceleration at a time (t) on the line segment initial_position, final_position .. py:method:: calculate() Builds s_space and t_space from the ramp accelerations. .. py:method:: jerk(t) Instantaneous jerk (da/dt) at a time (t) on the line segment initial_position, final_position .. py:method:: position(t) Instantaneous position at a time (t) on the line segment initial_position, final_position .. py:method:: velocity(t) Instantaneous velocity at a time (t) on the line segment initial_position, final_position .. py:attribute:: final_velocity :value: None .. py:attribute:: initial_velocity .. py:attribute:: max_acceleration :value: None .. py:attribute:: max_jerk :value: None .. py:attribute:: s_space :value: () .. py:attribute:: t_space :value: () .. py:attribute:: target_velocity .. py:class:: Trapezoid(initial_position: tuple, final_position: tuple, initial_velocity: float, target_velocity: float, final_velocity: float = None, max_acceleration: float = None, _distance: float = None, **_) Bases: :py:obj:`MotionSegment` Models a trapezoidal motion profile for a linear segment. This profile consists of three phases: 1. A constant acceleration phase to ramp up to a maximum velocity. 2. A constant velocity phase. 3. A constant deceleration phase to ramp down to a final velocity. The profile operates in one of two modes: - **Distance-Limited:** If `max_acceleration` is not provided, the profile shape is determined by assuming the ramp-up and ramp-down phases each occupy one-third of the total move distance. - **Acceleration-Limited:** If `max_acceleration` is provided, this limit is enforced. This may result in a "triangular" or "linear ramp" profile if the move is too short to reach the requested maximum velocity. :param initial_position: The starting coordinate of the move. :type initial_position: tuple :param final_position: The ending coordinate of the move. :type final_position: tuple :param initial_velocity: The velocity at the start of the move. :type initial_velocity: float :param target_velocity: The target cruising velocity for the move. :type target_velocity: float :param final_velocity: The velocity at the end of the move. If None, defaults to `initial_velocity`. :type final_velocity: float, optional :param max_acceleration: The maximum allowed acceleration and deceleration. If None, the profile is distance-limited. :type max_acceleration: float, optional .. py:method:: acceleration(t: float) Instantaneous acceleration at a time (t) on the line segment initial_position, final_position .. py:method:: calculate() Calculates the final position and time frames for the profile. This method uses the finalized ramp distances (`self._ramp`) and the calculated accelerations to determine the duration of each motion phase. It populates the `s_space` (position) and `t_space` (time) tuples that define the boundaries of the three motion phases. .. py:method:: position(t) Instantaneous position at a time (t) on the line segment initial_position, final_position .. py:method:: velocity(t: float) Instantaneous velocity at a time (t) on the line segment initial_position, final_position .. py:attribute:: final_velocity :value: None .. py:attribute:: initial_velocity .. py:attribute:: max_acceleration :value: None .. py:attribute:: s_space :value: () .. py:attribute:: t_space :value: () .. py:attribute:: target_velocity .. py:function:: extract_motion_scalars(operation) -> dict Extract per-segment velocity scalars from a processed operation. Returns three numpy arrays aligned 1:1 with ``operation.process_data``: - ``initial_velocity`` — entry speed into the segment (units/s). - ``target_velocity`` — commanded cruise ceiling (units/s). - ``final_velocity`` — exit speed from the segment (units/s). ``None`` entries in ``motion_profile`` (zero-length moves) produce ``NaN``. ``ConstantVelocity`` segments, which have no separate target/final velocity, use their single ``v`` attribute for all three fields. :param operation: A processed ``Operation`` whose ``motion_profile`` list has been populated by ``MotionProfile.from_operation()``. :returns: Dict with keys ``"initial_velocity"``, ``"target_velocity"``, ``"final_velocity"``, each a shape ``(N,)`` float64 array. :raises ValueError: If ``operation.motion_profile`` is empty or not set.