def random_3d_path(steps: int = 100, max_step_size: float = 1.0, max_heading: float = math.pi / 2.0, max_pitch: float = math.pi / 8.0, retarget: int = 20) -> Iterable[Vec3]: """ Returns a random 3D path as iterable of :class:`~ezdxf.math.Vec3` objects. Args: steps: count of vertices to generate max_step_size: max step size max_heading: limit heading angle change per step to ± max_heading/2, rotation about the z-axis in radians max_pitch: limit pitch angle change per step to ± max_pitch/2, rotation about the x-axis in radians retarget: specifies steps before changing global walking target """ max_ = max_step_size * steps def next_global_target(): return Vec3((rnd(max_), rnd(max_), rnd(max_))) walker = Vec3() target = next_global_target() for i in range(steps): if i % retarget == 0: target = target + next_global_target() angle = (target - walker).angle length = max_step_size * random.random() heading_angle = angle + rnd_perlin(max_heading, walker) next_step = Vec3.from_angle(heading_angle, length) pitch_angle = rnd_perlin(max_pitch, walker) walker += Matrix44.x_rotate(pitch_angle).transform(next_step) yield walker
def tangent(self, t: float) -> Vec3: """ Get tangent at distance `t` as :class.`Vec3` object. """ angle = t**2 / (2. * self.curvature_powers[2]) return Vec3.from_angle(angle)
def to_ocs_angle_rad(self, angle: float) -> float: """Transforms `angle` from current UCS to the parent coordinate system (most likely the WCS) including the transformation to the OCS established by the extrusion vector :attr:`UCS.uz`. Args: angle: in UCS in radians """ return self.ucs_direction_to_ocs_direction(Vec3.from_angle(angle)).angle
def linear_measurement(p1: Vec3, p2: Vec3, angle: float = 0, ocs: 'OCS' = None) -> float: """ Returns distance from `p1` to `p2` projected onto ray defined by `angle`, `angle` in radians in the xy-plane. """ if ocs is not None and ocs.uz != (0, 0, 1): p1 = ocs.to_wcs(p1) p2 = ocs.to_wcs(p2) # angle in OCS xy-plane ocs_direction = Vec3.from_angle(angle) measurement_direction = ocs.to_wcs(ocs_direction) else: # angle in WCS xy-plane measurement_direction = Vec3.from_angle(angle) t1 = measurement_direction.project(p1) t2 = measurement_direction.project(p2) return (t2 - t1).magnitude
def cubic_bezier_arc_parameters(start_angle: float, end_angle: float, segments: int = 1) -> Sequence[Vec3]: """ Yields cubic Bézier-curve parameters for a circular 2D arc with center at (0, 0) and a radius of 1 in the form of [start point, 1. control point, 2. control point, end point]. Args: start_angle: start angle in radians end_angle: end angle in radians (end_angle > start_angle!) segments: count of Bèzier-curve segments, at least one segment for each quarter (pi/2) """ if segments < 1: raise ValueError('Invalid argument segments (>= 1).') delta_angle = end_angle - start_angle if delta_angle > 0: arc_count = max(math.ceil(delta_angle / math.pi * 2.0), segments) else: raise ValueError('Delta angle from start- to end angle has to be > 0.') segment_angle = delta_angle / arc_count tangent_length = TANGENT_FACTOR * math.tan(segment_angle / 4.0) angle = start_angle end_point = None for _ in range(arc_count): start_point = Vec3.from_angle( angle) if end_point is None else end_point angle += segment_angle end_point = Vec3.from_angle(angle) control_point_1 = start_point + (-start_point.y * tangent_length, start_point.x * tangent_length) control_point_2 = end_point + (end_point.y * tangent_length, -end_point.x * tangent_length) yield start_point, control_point_1, control_point_2, end_point
def transform_angle(self, angle: float) -> float: """ Returns angle (in radians) from old OCS transformed into new OCS. """ return self.transform_direction( Vec3.from_angle(angle)).angle % math.tau