def transform_extrusion(extrusion: 'Vertex', m: Matrix44) -> Tuple[Vec3, bool]: """ Transforms the old `extrusion` vector into a new extrusion vector. Returns the new extrusion vector and a boolean value: ``True`` if the new OCS established by the new extrusion vector has a uniform scaled xy-plane, else ``False``. The new extrusion vector is perpendicular to plane defined by the transformed x- and y-axis. Args: extrusion: extrusion vector of the old OCS m: transformation matrix Returns: """ ocs = OCS(extrusion) ocs_x_axis_in_wcs = ocs.to_wcs(X_AXIS) ocs_y_axis_in_wcs = ocs.to_wcs(Y_AXIS) x_axis, y_axis = m.transform_directions( (ocs_x_axis_in_wcs, ocs_y_axis_in_wcs)) # Not sure if this is the correct test for a uniform scaled xy-plane is_uniform = math.isclose(x_axis.magnitude_square, y_axis.magnitude_square, abs_tol=1e-9) new_extrusion = x_axis.cross(y_axis).normalize() return new_extrusion, is_uniform
def transform(self, m: Matrix44) -> None: """ Transform ellipse in place by transformation matrix `m`. """ new_center = m.transform(self.center) # 2021-01-28 removed % math.tau old_start_param = start_param = self.start_param old_end_param = end_param = self.end_param old_minor_axis = minor_axis(self.major_axis, self.extrusion, self.ratio) new_major_axis, new_minor_axis = m.transform_directions( (self.major_axis, old_minor_axis)) # Original ellipse parameters stay untouched until end of transformation dot_product = new_major_axis.normalize().dot( new_minor_axis.normalize()) if abs(dot_product) > 1e-6: new_major_axis, new_minor_axis, new_ratio = rytz_axis_construction( new_major_axis, new_minor_axis) new_extrusion = new_major_axis.cross(new_minor_axis).normalize() adjust_params = True else: # New axis are nearly orthogonal: new_ratio = new_minor_axis.magnitude / new_major_axis.magnitude # New normal vector: new_extrusion = new_major_axis.cross(new_minor_axis).normalize() # Calculate exact minor axis: new_minor_axis = minor_axis(new_major_axis, new_extrusion, new_ratio) adjust_params = False if adjust_params and not math.isclose( start_param, end_param, abs_tol=1e-9): # open ellipse, adjusting start- and end parameter x_axis = new_major_axis.normalize() y_axis = new_minor_axis.normalize() # TODO: use ellipse_param_span()? # 2021-01-28 this is possibly the source of errors! old_param_span = (end_param - start_param) % math.tau def param(vec: 'Vec3') -> float: dy = y_axis.dot(vec) / new_ratio # adjust to circle dx = x_axis.dot(vec) return math.atan2(dy, dx) % math.tau # transformed start- and end point of old ellipse start_point = m.transform( vertex(start_param, self.major_axis, old_minor_axis, self.center, self.ratio)) end_point = m.transform( vertex(end_param, self.major_axis, old_minor_axis, self.center, self.ratio)) start_param = param(start_point - new_center) end_param = param(end_point - new_center) # Test if drawing the correct side of the curve if not math.isclose(old_param_span, math.pi, abs_tol=1e-9): # Equal param span check works well, except for a span of exact # pi (180 deg). # TODO: use ellipse_param_span()? # 2021-01-28 this is possibly the source of errors! new_param_span = (end_param - start_param) % math.tau if not math.isclose( old_param_span, new_param_span, abs_tol=1e-9): start_param, end_param = end_param, start_param else: # param span is exact pi (180 deg) # expensive but it seem to work: old_chk_point = m.transform( vertex( mid_param(old_start_param, old_end_param), self.major_axis, old_minor_axis, self.center, self.ratio, )) new_chk_point = vertex( mid_param(start_param, end_param), new_major_axis, new_minor_axis, new_center, new_ratio, ) if not old_chk_point.isclose(new_chk_point, abs_tol=1e-9): start_param, end_param = end_param, start_param if new_ratio > 1: new_major_axis = minor_axis(new_major_axis, new_extrusion, new_ratio) new_ratio = 1.0 / new_ratio new_minor_axis = minor_axis(new_major_axis, new_extrusion, new_ratio) if not (math.isclose(start_param, 0) and math.isclose(end_param, math.tau)): start_param -= pi2 end_param -= pi2 # TODO: remove normalize start- and end params? # 2021-01-28 this is possibly the source of errors! start_param = start_param % math.tau end_param = end_param % math.tau if math.isclose(start_param, end_param): start_param = 0.0 end_param = math.tau self.center = new_center self.major_axis = new_major_axis self.minor_axis = new_minor_axis self.extrusion = new_extrusion self.ratio = new_ratio self.start_param = start_param self.end_param = end_param