def add_lines(msp, center: Vec3, radius: float, start_angle: float, end_angle: float): attribs = {"color": 7} start_point = center + Vec3.from_deg_angle(start_angle) * radius end_point = center + Vec3.from_deg_angle(end_angle) * radius msp.add_line(center, start_point, dxfattribs=attribs) msp.add_line(center, end_point, dxfattribs=attribs)
def transform(self, m: Matrix44) -> 'Text': """ Transform TEXT entity by transformation matrix `m` inplace. .. versionadded:: 0.13 """ dxf = self.dxf if not dxf.hasattr('align_point'): dxf.align_point = dxf.insert ocs = OCSTransform(self.dxf.extrusion, m) dxf.insert = ocs.transform_vertex(dxf.insert) dxf.align_point = ocs.transform_vertex(dxf.align_point) old_rotation = dxf.rotation new_rotation = ocs.transform_deg_angle(old_rotation) x_scale = ocs.transform_length(Vec3.from_deg_angle(old_rotation)) y_scale = ocs.transform_length( Vec3.from_deg_angle(old_rotation + 90.0)) if not ocs.scale_uniform: oblique_vec = Vec3.from_deg_angle( old_rotation + 90.0 - dxf.oblique) new_oblique_deg = new_rotation + 90.0 - ocs.transform_direction( oblique_vec).angle_deg dxf.oblique = new_oblique_deg y_scale *= math.cos(math.radians(new_oblique_deg)) dxf.width *= x_scale / y_scale dxf.height *= y_scale dxf.rotation = new_rotation if dxf.hasattr('thickness'): # can be negative dxf.thickness = ocs.transform_length((0, 0, dxf.thickness), reflection=dxf.thickness) dxf.extrusion = ocs.new_extrusion return self
def add_axis(attribs: Dict, m: Matrix44 = None): start = -X_AXIS * arrow_length / 2 end = X_AXIS * arrow_length / 2 leg1 = Vec3.from_deg_angle(180 - leg_angle) * leg_length leg2 = Vec3.from_deg_angle(180 + leg_angle) * leg_length lines = [ block.add_line(start, end, dxfattribs=attribs), block.add_line(end, end + leg1, dxfattribs=attribs), block.add_line(end, end + leg2, dxfattribs=attribs), ] if m is not None: for line in lines: line.transform(m)
def usr_location_relative(angle: float, rotation: float = None): doc = ezdxf.new(DXFVERSION, setup=True) msp = doc.modelspace() x_dist = 10 radius = 3.0 distance = 1.0 for dimtad, y_dist, leader in [ [0, 0, False], [0, 10, True], [4, 20, True], ]: for count in range(8): center = Vec3(x_dist * count, y_dist) main_angle = 45.0 * count dim = add_arc_dim( msp, center=center, angle=main_angle, delta=angle / 2.0, radius=radius, distance=distance, text_rotation=rotation, override={"dimtad": dimtad}, ) # user location relative to center of dimension line: usr_location = Vec3.from_deg_angle(main_angle, 2.0) dim.set_location(usr_location, leader=leader, relative=True) dim.render(discard=BRICSCAD) doc.set_modelspace_vport(height=100, center=(x_dist * 4, 40)) rstr = "" if rotation is not None: rstr = f"rot_{rotation}_" doc.saveas(OUTDIR / f"dim_arc_usr_loc_relative_{rstr}_{DXFVERSION}.dxf")
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 test_reflections(self, s, e, rotation, sx, sy): m = Matrix44.chain( Matrix44.scale(sx, sy, 1), Matrix44.z_rotate(rotation), ) expected_start = m.transform(Vec3.from_deg_angle(s)) expected_end = m.transform(Vec3.from_deg_angle(e)) expected_angle_span = arc_angle_span_deg(s, e) ocs = OCSTransform(Z_AXIS, m) new_s, new_e = ocs.transform_ccw_arc_angles_deg(s, e) wcs_start = ocs.new_ocs.to_wcs(Vec3.from_deg_angle(new_s)) wcs_end = ocs.new_ocs.to_wcs(Vec3.from_deg_angle(new_e)) assert arc_angle_span_deg(new_s, new_e) == pytest.approx( expected_angle_span ) assert wcs_start.isclose(expected_start) assert wcs_end.isclose(expected_end)
def add_dim(x, y, radius, dimtad): center = Vec3(x, y) msp.add_circle((x, y), radius=3) dim_location = center + Vec3.from_deg_angle(angle, radius) dim = msp.add_diameter_dim(center=(x, y), radius=3, location=dim_location, dimstyle='EZ_RADIUS', override={ 'dimtad': dimtad, }) dim.render(discard=BRICSCAD)
def to_ocs_angle_deg(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 degrees """ return self.ucs_direction_to_ocs_direction(Vec3.from_deg_angle(angle)).angle_deg
def add_dim_user(msp, x, y, distance, override): center = Vec3(x, y) msp.add_circle(center, radius=RADIUS) location = center + Vec3.from_deg_angle(45, distance) add_mark(msp, location) dim = msp.add_radius_dim(center=center, radius=RADIUS, location=location, dimstyle='EZ_RADIUS', override=override) dim.render()
def vertices(self, angles: Iterable[float]) -> Iterable[Vec3]: """ Yields vertices of the circle for iterable `angles` in WCS. Args: angles: iterable of angles in OCS as degrees, angle goes counter clockwise around the extrusion vector, ocs x-axis = 0 deg. """ ocs = self.ocs() for angle in angles: v = Vec3.from_deg_angle(angle, self.dxf.radius) + self.dxf.center yield ocs.to_wcs(v)
def vertices(self, angles: Iterable[float]) -> Iterable[Vec3]: """Yields vertices of the circle for iterable `angles` in WCS. Args: angles: iterable of angles in OCS as degrees, angle goes counter clockwise around the extrusion vector, OCS x-axis = 0 deg. """ ocs = self.ocs() radius: float = abs(self.dxf.radius) # AutoCAD ignores the sign too center = Vec3(self.dxf.center) for angle in angles: yield ocs.to_wcs(Vec3.from_deg_angle(angle, radius) + center)
def add_dim(x, y, radius, dimtad): center = Vec3(x, y) msp.add_circle((x, y), radius=3) dim_location = center + Vec3.from_deg_angle(angle, radius) dim = msp.add_radius_dim( center=(x, y), radius=3, location=dim_location, dimstyle='EZ_RADIUS', override={ 'dimtad': dimtad, 'dimtih': 1, # force text inside horizontal }) dim.render(discard=BRICSCAD)
def arc_3p_default(distance: float = 2.0): doc = ezdxf.new(DXFVERSION, setup=True) msp = doc.modelspace() radius = 5 data = [ [Vec3(0, 0), 60, 120], [Vec3(10, 0), 300, 240], [Vec3(20, 0), 240, 300], ] for dimtad, offset in [(1, (0, 20)), (0, (0, 0)), (4, (0, -20))]: for center, start_angle, end_angle in data: center += Vec3(offset) dir1 = Vec3.from_deg_angle(start_angle) dir2 = Vec3.from_deg_angle(end_angle) # calculate defpoints from parameters of the "cra" example: p1 = center + dir1 * radius p2 = center + dir2 * radius base = center + dir1.lerp(dir2) * (radius + distance) add_lines(msp, center, radius, start_angle, end_angle) add_arc(msp, center, radius, start_angle, end_angle) msp.add_arc_dim_3p( base, center, p1, p2, override={ "dimtad": dimtad, "dimtxt": 1, "dimasz": 1, "dimgap": 0.25, }, ).render(discard=BRICSCAD) doc.set_modelspace_vport(height=70) doc.saveas(OUTDIR / f"dim_arc_3p_{DXFVERSION}.dxf")
def get_text_direction(self) -> Vec3: """Returns the horizontal text direction as :class:`~ezdxf.math.Vec3` object, even if only the text rotation is defined. """ dxf = self.dxf # "text_direction" has higher priority than "rotation" if dxf.hasattr("text_direction"): return dxf.text_direction if dxf.hasattr("rotation"): # 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) return OCS(dxf.extrusion).to_wcs(Vec3.from_deg_angle(dxf.rotation)) return X_AXIS
def add_dim(x, y, radius, dimtad): center = Vec3(x, y) msp.add_circle((x, y), radius=3) dim_location = center + Vec3.from_deg_angle(angle, radius) dim = msp.add_radius_dim( center=(x, y), radius=3, location=dim_location, dimstyle="EZ_RADIUS", override={ "dimtad": dimtad, "dimtoh": 1, # force text outside horizontal }, ) dim.render(discard=BRICSCAD)
def get_ucs() -> UCS: """ Create local coordinate system: origin = insertion point z-axis = extrusion vector x-axis = text_direction or text rotation, text rotation requires extrusion vector == (0, 0, 1) or treatment like an OCS? """ origin = mtext.dxf.insert z_axis = mtext.dxf.extrusion # default is Z_AXIS x_axis = X_AXIS if mtext.dxf.hasattr('text_direction'): x_axis = mtext.dxf.text_direction elif mtext.dxf.hasattr('rotation'): # TODO: what if extrusion vector is not (0, 0, 1) x_axis = Vec3.from_deg_angle(mtext.dxf.rotation) z_axis = Z_AXIS return UCS(origin=origin, ux=x_axis, uz=z_axis)
def _create_linked_columns(self) -> None: """ Create linked MTEXT columns for DXF versions before R2018. """ # creates virtual MTEXT entities dxf = self.dxf attribs = self.dxfattribs(drop={'handle', 'owner'}) doc = self.doc cols = self._columns insert = dxf.get('insert', Vec3()) default_direction = Vec3.from_deg_angle(dxf.get('rotation', 0)) text_direction = Vec3(dxf.get('text_direction', default_direction)) offset = text_direction.normalize(cols.width + cols.gutter_width) linked_columns = cols.linked_columns for _ in range(cols.count - 1): insert += offset column = MText.new(dxfattribs=attribs, doc=doc) column.dxf.insert = insert linked_columns.append(column)
def ordinate_ucs( filename: str, rotate: float = 30.0, ): doc = ezdxf.new(DXFVERSION, setup=True) dimstyle = doc.dimstyles.duplicate_entry("EZDXF", "ORD_CENTER") dimstyle.dxf.dimtad = 0 msp = doc.modelspace() for origin in [Vec3(5, 20), Vec3(0, 0), Vec3(-5, -20)]: ucs = UCS(origin, ux=Vec3.from_deg_angle(rotate), uz=(0, 0, 1)) msp.add_ordinate_x_dim(feature_location=(3, 2), offset=(1, 2), dimstyle="ORD_CENTER").render(ucs=ucs) msp.add_ordinate_y_dim(feature_location=(3, 2), offset=(1, -2), dimstyle="ORD_CENTER").render(ucs=ucs) doc.set_modelspace_vport(height=70) doc.saveas(OUTDIR / f"{filename}_{DXFVERSION}.dxf")
import ezdxf from ezdxf.math import UCS, Vec3 from pathlib import Path OUT_DIR = Path('~/Desktop/Outbox').expanduser() doc = ezdxf.new('R2010') msp = doc.modelspace() # Thickness for text works only with shx fonts not with true type fonts doc.styles.new('TXT', dxfattribs={'font': 'romans.shx'}) ucs = UCS(origin=(0, 2, 2), ux=(1, 0, 0), uz=(0, 1, 1)) # calculation of text direction as angle in OCS: # convert text rotation in degree into a vector in UCS text_direction = Vec3.from_deg_angle(-45) # transform vector into OCS and get angle of vector in xy-plane rotation = ucs.to_ocs(text_direction).angle_deg text = msp.add_text( text="TEXT", dxfattribs={ # text rotation angle in degrees in OCS 'rotation': rotation, 'extrusion': ucs.uz, 'thickness': .333, 'color': 1, 'style': 'TXT', }) # set text position in OCS text.set_pos(ucs.to_ocs((0, 0, 0)), align='MIDDLE_CENTER')
from pathlib import Path OUT_DIR = Path('~/Desktop/Outbox').expanduser() doc = ezdxf.new('R2010') msp = doc.modelspace() ucs = UCS(origin=(0, 2, 2), ux=(1, 0, 0), uz=(0, 1, 1)) msp.add_arc(center=ucs.to_ocs((0, 0)), radius=1, start_angle=ucs.to_ocs_angle_deg(45), end_angle=ucs.to_ocs_angle_deg(270), dxfattribs={ 'extrusion': ucs.uz, 'color': 1, }) center = ucs.to_wcs((0, 0)) msp.add_line( start=center, end=ucs.to_wcs(Vec3.from_deg_angle(45)), dxfattribs={'color': 1}, ) msp.add_line( start=center, end=ucs.to_wcs(Vec3.from_deg_angle(270)), dxfattribs={'color': 1}, ) ucs.render_axis(msp) doc.saveas(OUT_DIR / 'ocs_arc.dxf')
# Create a special DIMSTYLE for "vertical" centered measurement text: dimstyle = doc.dimstyles.duplicate_entry("EZDXF", "ORD_CENTER") dimstyle.dxf.dimtad = 0 # "vertical" centered measurement text # Add a rectangle: width=4, height = 2.5, lower left corner is WCS(x=2, y=3), # rotated about 30 degrees: origin = Vec3(2, 3) msp.add_lwpolyline(forms.translate(forms.rotate(forms.box(4, 2.5), 30), origin), close=True) # Define the rotated local render UCS. # The origin is the lower-left corner of the rectangle and the axis are # aligned to the rectangle edges: # The y-axis "uy" is calculated automatically by the right-hand rule. ucs = UCS(origin, ux=Vec3.from_deg_angle(30), uz=(0, 0, 1)) # Add a x-type ordinate DIMENSION with local feature locations: # the origin is now the origin of the UCS, which is (0, 0) the default value of # "origin" and the feature coordinates are located in the UCS: msp.add_ordinate_x_dim( # lower left corner feature_location=(0, 0), # feature location in the UCS offset=(0.25, -2), # # leader with a "knee" dimstyle="ORD_CENTER", ).render(ucs=ucs) # Important when using a render UCS! msp.add_ordinate_x_dim( # lower right corner feature_location=(4, 0), # feature location in the UCS offset=(0.25, -2), # leader with a "knee" dimstyle="ORD_CENTER",
START_ANGLE = 45 END_ANGLE = 270 msp.add_arc( center=CENTER, radius=1, start_angle=START_ANGLE, end_angle=END_ANGLE, dxfattribs={ 'color': 6 }, ).transform(ucs.matrix) msp.add_line( start=CENTER, end=Vec3.from_deg_angle(START_ANGLE), dxfattribs={ 'color': 6 }, ).transform(ucs.matrix) msp.add_line( start=CENTER, end=Vec3.from_deg_angle(END_ANGLE), dxfattribs={ 'color': 6 }, ).transform(ucs.matrix) ucs.render_axis(msp) doc.saveas(OUT_DIR / 'ucs_arc.dxf')
}) zoom.extents(msp) doc.saveas(DIR / 'concept-0-fit-points-only.dxf') # ------------------------------------------------------------------------------ # SPLINE from fit points WITH given end tangents. # ------------------------------------------------------------------------------ # 2. Store fit points, start- and end tangent values in DXF file: doc, msp = setup() # Tangent estimation method: "Total Chord Length", # returns sum of chords for m1 and m2 m1, m2 = estimate_end_tangent_magnitude(points, method='chord') # Multiply tangent vectors by total chord length for global interpolation: start_tangent = Vec3.from_deg_angle(100) * m1 end_tangent = Vec3.from_deg_angle(-100) * m2 # Interpolate control vertices from fit points and end derivatives as constraints s = global_bspline_interpolation(points, degree=3, tangents=(start_tangent, end_tangent)) msp.add_spline(dxfattribs={ 'color': 4, 'layer': 'Global Interpolation' }).apply_construction_tool(s) # Result matches the BricsCAD interpolation if fit points, start- and end # tangents are stored explicit in the DXF file. spline = msp.add_spline(points, degree=3, dxfattribs={
OUT_DIR = Path('~/Desktop/Outbox').expanduser() doc = ezdxf.new('R2010') msp = doc.modelspace() # The center of the pentagon should be (0, 2, 2), and the shape is # rotated around x-axis about 45 degree, to accomplish this I use an # UCS with z-axis (0, 1, 1) and an x-axis parallel to WCS x-axis. ucs = UCS( origin=(0, 2, 2), # center of pentagon ux=(1, 0, 0), # x-axis parallel to WCS x-axis uz=(0, 1, 1), # z-axis ) # calculating corner points in local (UCS) coordinates points = [Vec3.from_deg_angle((360 / 5) * n) for n in range(5)] # converting UCS into OCS coordinates ocs_points = list(ucs.points_to_ocs(points)) # LWPOLYLINE accepts only 2D points and has an separated DXF attribute elevation. # All points have the same z-axis (elevation) in OCS! elevation = ocs_points[0].z msp.add_lwpolyline( points=ocs_points, format='xy', # ignore z-axis dxfattribs={ 'elevation': elevation, 'extrusion': ucs.uz, 'closed': True, 'color': 1,