def _virtual_polyline_entities(points, elevation: float, extrusion: Vector, dxfattribs: dict, doc) -> Iterable[Union['Line', 'Arc']]: ocs = OCS(extrusion) if extrusion else OCS() prev_point = None prev_bulge = None for x, y, bulge in points: point = Vector(x, y, elevation) if prev_point is None: prev_point = point prev_bulge = bulge continue attribs = dict(dxfattribs) if prev_bulge != 0: center, start_angle, end_angle, radius = bulge_to_arc( prev_point, point, prev_bulge) attribs['center'] = Vector(center.x, center.y, elevation) attribs['radius'] = radius attribs['start_angle'] = math.degrees(start_angle) attribs['end_angle'] = math.degrees(end_angle) if extrusion: attribs['extrusion'] = extrusion yield factory.new(dxftype='ARC', dxfattribs=attribs, doc=doc) else: attribs['start'] = ocs.to_wcs(prev_point) attribs['end'] = ocs.to_wcs(point) yield factory.new(dxftype='LINE', dxfattribs=attribs, doc=doc) prev_point = point prev_bulge = bulge
def _virtual_polyline_entities(points, elevation: float, extrusion: Vec3, dxfattribs: dict, doc) -> Iterable[Union["Line", "Arc"]]: ocs = OCS(extrusion) if extrusion else OCS() prev_point = None prev_bulge = None for x, y, bulge in points: point = Vec3(x, y, elevation) if prev_point is None: prev_point = point prev_bulge = bulge continue attribs = dict(dxfattribs) if prev_bulge != 0: center, start_angle, end_angle, radius = bulge_to_arc( prev_point, point, prev_bulge) if radius > 0: attribs["center"] = Vec3(center.x, center.y, elevation) attribs["radius"] = radius attribs["start_angle"] = math.degrees(start_angle) attribs["end_angle"] = math.degrees(end_angle) if extrusion: attribs["extrusion"] = extrusion yield factory.new(dxftype="ARC", dxfattribs=attribs, doc=doc) else: attribs["start"] = ocs.to_wcs(prev_point) attribs["end"] = ocs.to_wcs(point) yield factory.new(dxftype="LINE", dxfattribs=attribs, doc=doc) prev_point = point prev_bulge = bulge
def get_draw_angles(start: float, end: float, extrusion: Vector): if extrusion.isclose(Z_AXIS): return start, end else: ocs = OCS(extrusion) s = ocs.to_wcs(Vector.from_angle(start)) e = ocs.to_wcs(Vector.from_angle(end)) return normalize_angle(e.angle), normalize_angle(s.angle)
class OCSTransform: def __init__(self, extrusion: Vec3 = None, m: Matrix44 = None): self.m = m if extrusion is None: self.old_ocs = None self.scale_uniform = False self.new_ocs = None else: self.old_ocs = OCS(extrusion) new_extrusion, self.scale_uniform = transform_extrusion(extrusion, m) self.new_ocs = OCS(new_extrusion) @property def old_extrusion(self) -> Vec3: return self.old_ocs.uz @property def new_extrusion(self) -> Vec3: return self.new_ocs.uz @classmethod def from_ocs(cls, old: OCS, new: OCS, m: Matrix44) -> 'OCSTransform': ocs = cls() ocs.m = m ocs.old_ocs = old ocs.new_ocs = new return ocs def transform_length(self, length: 'Vertex', reflection=1.0) -> float: """ Returns magnitude of `length` direction vector transformed from old OCS into new OCS including `reflection` correction applied. """ return self.m.transform_direction(self.old_ocs.to_wcs(length)).magnitude * sign(reflection) transform_scale_factor = transform_length def transform_vertex(self, vertex: 'Vertex') -> Vec3: """ Returns vertex transformed from old OCS into new OCS. """ return self.new_ocs.from_wcs(self.m.transform(self.old_ocs.to_wcs(vertex))) def transform_2d_vertex(self, vertex: 'Vertex', elevation: float) -> Vec2: """ Returns 2D vertex transformed from old OCS into new OCS. """ v = Vec3(vertex).replace(z=elevation) return self.new_ocs.from_wcs(self.m.transform(self.old_ocs.to_wcs(v))).vec2 def transform_direction(self, direction: 'Vertex') -> Vec3: """ Returns direction transformed from old OCS into new OCS. """ return self.new_ocs.from_wcs(self.m.transform_direction(self.old_ocs.to_wcs(direction))) 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 def transform_deg_angle(self, angle: float) -> float: """ Returns angle (in degrees) from old OCS transformed into new OCS. """ return math.degrees(self.transform_angle(math.radians(angle)))
def transform(self, m: Matrix44) -> 'MText': """ Transform the MTEXT entity by transformation matrix `m` inplace. """ dxf = self.dxf old_extrusion = Vec3(dxf.extrusion) new_extrusion, _ = transform_extrusion(old_extrusion, m) if dxf.hasattr('rotation') and not dxf.hasattr('text_direction'): # MTEXT is not an OCS entity, but I don't know how else to convert # a rotation angle for an entity just defined by an extrusion vector. # It's correct for the most common case: extrusion=(0, 0, 1) ocs = OCS(old_extrusion) dxf.text_direction = ocs.to_wcs(Vec3.from_deg_angle(dxf.rotation)) dxf.discard('rotation') old_text_direction = Vec3(dxf.text_direction) new_text_direction = m.transform_direction(old_text_direction) old_char_height_vec = old_extrusion.cross( old_text_direction).normalize(dxf.char_height) new_char_height_vec = m.transform_direction(old_char_height_vec) oblique = new_text_direction.angle_between(new_char_height_vec) dxf.char_height = new_char_height_vec.magnitude * math.sin(oblique) if dxf.hasattr('width'): width_vec = old_text_direction.normalize(dxf.width) dxf.width = m.transform_direction(width_vec).magnitude dxf.insert = m.transform(dxf.insert) dxf.text_direction = new_text_direction dxf.extrusion = new_extrusion return self
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 _to_wcs(self, ocs: OCS, elevation: float): self._start = ocs.to_wcs(self._start.replace(z=elevation)) for i, cmd in enumerate(self._commands): new_cmd = [cmd[0]] new_cmd.extend( ocs.points_to_wcs(p.replace(z=elevation) for p in cmd[1:])) self._commands[i] = tuple(new_cmd)
def from_hatch_boundary_path( boundary: AbstractBoundaryPath, ocs: OCS = None, elevation: float = 0, offset: Vec3 = NULLVEC, # ocs offset! ) -> "Path": """Returns a :class:`Path` object from a :class:`~ezdxf.entities.Hatch` polyline- or edge path. .. versionchanged:: 17.1 Attaches the boundary state to each path as :class:`ezdxf.lldxf.const.BoundaryPathState`. """ if isinstance(boundary, EdgePath): p = from_hatch_edge_path(boundary, ocs, elevation) elif isinstance(boundary, PolylinePath): p = from_hatch_polyline_path(boundary, ocs, elevation) else: raise TypeError(type(boundary)) if offset and ocs is not None: # only for MPOLYGON # assume offset is in OCS offset = ocs.to_wcs(offset.replace(z=elevation)) p = p.transform(Matrix44.translate(offset.x, offset.y, offset.z)) # attach path type information p.user_data = const.BoundaryPathState.from_flags(boundary.path_type_flags) return p
def from_arc( cls, center: "Vertex" = NULLVEC, radius: float = 1, extrusion: "Vertex" = Z_AXIS, start_angle: float = 0, end_angle: float = 360, ccw: bool = True, ) -> "ConstructionEllipse": """Returns :class:`ConstructionEllipse` from arc or circle. Arc and Circle parameters defined in OCS. Args: center: center in OCS radius: arc or circle radius extrusion: OCS extrusion vector start_angle: start angle in degrees end_angle: end angle in degrees ccw: arc curve goes counter clockwise from start to end if ``True`` """ radius = abs(radius) if NULLVEC.isclose(extrusion): raise ValueError(f"Invalid extrusion: {str(extrusion)}") ratio = 1.0 ocs = OCS(extrusion) center = ocs.to_wcs(center) # Major axis along the OCS x-axis. major_axis = ocs.to_wcs(Vec3(radius, 0, 0)) # No further adjustment of start- and end angle required. start_param = math.radians(start_angle) end_param = math.radians(end_angle) return cls( center, major_axis, extrusion, ratio, start_param, end_param, bool(ccw), )
def test_ocs_to_wcs(): ocs = OCS(EXTRUSION) wcs = ocs.to_wcs((9.41378764657076, 13.15481838975576, 0.8689258932616031)) assert is_close_points( wcs, (-9.56460754, 8.44764172, 9.97894327), places=6, ) assert is_close_points( ocs.to_wcs((9.41378764657076, 1.745643639268379, 0.8689258932616031)), (-1.60085321, 9.29648008, 1.85322122), places=6, ) assert is_close_points( ocs.to_wcs((9.41378764657076, 4.552784531093068, 0.8689258932616031)), (-3.56027455, 9.08762984, 3.85249348), places=6, ) assert is_close_points( ocs.to_wcs((9.41378764657076, 7.386888158025531, 0.8689258932616031)), (-5.53851623, 8.87677359, 5.87096886), places=6, )
def test_circle_user_ocs(): center = (2, 3, 4) extrusion = (0, 1, 0) circle = Circle.new( dxfattribs={'center': center, 'extrusion': extrusion, 'thickness': 2}) ocs = OCS(extrusion) v = ocs.to_wcs(center) # (-2, 4, 3) v = Vector(v.x * 2, v.y * 4, v.z * 2) v += (1, 1, 1) # and back to OCS, extrusion is unchanged result = ocs.from_wcs(v) m = Matrix44.chain(Matrix44.scale(2, 4, 2), Matrix44.translate(1, 1, 1)) circle.transform(m) assert circle.dxf.center == result assert circle.dxf.extrusion == (0, 1, 0) assert circle.dxf.thickness == 8 # in WCS y-axis
def to_wcs(self, ocs: OCS, elevation: float): """ Transform path from given `ocs` to WCS coordinates inplace. """ self._start = ocs.to_wcs(self._start.replace(z=elevation)) for i, cmd in enumerate(self._commands): self._commands[i] = cmd.to_wcs(ocs, elevation)
def to_wcs(self, ocs: OCS, elevation: float): return CurveTo( end=ocs.to_wcs(self.end.replace(z=elevation)), ctrl1=ocs.to_wcs(self.ctrl1.replace(z=elevation)), ctrl2=ocs.to_wcs(self.ctrl2.replace(z=elevation)), )
def to_wcs(self, ocs: OCS, elevation: float): return LineTo(end=ocs.to_wcs(self.end.replace(z=elevation)))
def _to_wcs(self, ocs: OCS, elevation: float): self._start = ocs.to_wcs(self._start.replace(z=elevation)) for i, cmd in enumerate(self._commands): self._commands[i] = cmd.to_wcs(ocs, elevation)
def to_wcs(self, ocs: OCS, elevation: float) -> None: """Transform path from given `ocs` to WCS coordinates inplace.""" self._vertices = list( ocs.to_wcs(v.replace(z=elevation)) for v in self._vertices)