| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | """Provides functions to return the SVG representation of some shapes.""" |
| | |
| | |
| | |
| |
|
| | import math |
| | import lazy_loader.lazy_loader as lz |
| |
|
| | import FreeCAD as App |
| | import DraftVecUtils |
| | import WorkingPlane |
| | from draftutils import params |
| | from draftutils import utils |
| | from draftutils.messages import _msg, _wrn |
| |
|
| | |
| | Part = lz.LazyLoader("Part", globals(), "Part") |
| | DraftGeomUtils = lz.LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils") |
| | TechDraw = lz.LazyLoader("TechDraw", globals(), "TechDraw") |
| |
|
| | |
| | |
| |
|
| |
|
| | def get_proj(vec, plane=None): |
| | """Get a projection of the vector in the plane's u and v directions. |
| | |
| | TODO: check if the same function for SVG and DXF projection can be used |
| | so that this function is not just duplicated code. |
| | This function may also be present elsewhere, like `WorkingPlane` |
| | or `DraftGeomUtils`, so we should avoid code duplication. |
| | |
| | Parameters |
| | ---------- |
| | vec: Base::Vector3 |
| | An arbitrary vector that will be projected on the U and V directions. |
| | |
| | plane: WorkingPlane.PlaneBase |
| | Working plane. |
| | """ |
| | if not plane: |
| | return vec |
| |
|
| | nx = DraftVecUtils.project(vec, plane.u) |
| | lx = nx.Length |
| |
|
| | if abs(nx.getAngle(plane.u)) > 0.1: |
| | lx = -lx |
| |
|
| | ny = DraftVecUtils.project(vec, plane.v) |
| | ly = ny.Length |
| |
|
| | if abs(ny.getAngle(plane.v)) > 0.1: |
| | ly = -ly |
| |
|
| | |
| | |
| | return App.Vector(lx, ly, 0) |
| |
|
| |
|
| | def getProj(vec, plane=None): |
| | """Get a projection of a vector. DEPRECATED.""" |
| | utils.use_instead("get_proj") |
| | return get_proj(vec, plane) |
| |
|
| |
|
| | def get_discretized(edge, plane): |
| | """Get a discretized edge on a plane.""" |
| | pieces = params.get_param("svgDiscretization") |
| |
|
| | if pieces == 0: |
| | pieces = 10 |
| |
|
| | d = int(edge.Length / pieces) |
| | if d == 0: |
| | d = 1 |
| |
|
| | edata = "" |
| | for i in range(d + 1): |
| | _length = edge.LastParameter - edge.FirstParameter |
| | _point = edge.FirstParameter + float(i) / d * _length |
| | _vec = edge.valueAt(_point) |
| | v = get_proj(_vec, plane) |
| |
|
| | if not edata: |
| | edata += "M " + str(v.x) + " " + str(v.y) + " " |
| | else: |
| | edata += "L " + str(v.x) + " " + str(v.y) + " " |
| |
|
| | return edata |
| |
|
| |
|
| | def getDiscretized(edge, plane): |
| | """Get a discretized edge on a plane. DEPRECATED.""" |
| | utils.use_instead("get_discretized") |
| | return get_discretized(edge, plane) |
| |
|
| |
|
| | def _get_path_circ_ellipse( |
| | plane, edge, verts, edata, iscircle, isellipse, fill, stroke, linewidth, lstyle |
| | ): |
| | """Get the edge data from a path that is a circle or ellipse.""" |
| | if plane: |
| | drawing_plane_normal = plane.axis |
| | else: |
| | drawing_plane_normal = WorkingPlane.get_working_plane(update=False).axis |
| |
|
| | center = edge.Curve |
| | ax = center.Axis |
| |
|
| | |
| | _angle = math.degrees(ax.getAngle(drawing_plane_normal)) |
| | if round(_angle, 2) not in (0, 180): |
| | edata += get_discretized(edge, plane) |
| | return "edata", edata |
| |
|
| | |
| | occversion = Part.OCC_VERSION.split(".") |
| | done = False |
| | if int(occversion[0]) >= 7 and int(occversion[1]) >= 1: |
| | |
| | snip = TechDraw.projectToSVG(edge, drawing_plane_normal) |
| |
|
| | if snip: |
| | try: |
| | _a = snip.split('path d="')[1] |
| | _a = _a.split('"')[0] |
| | _a = _a.split("A")[1] |
| | A = "A " + _a |
| | except IndexError: |
| | pass |
| | |
| | |
| | |
| | |
| | |
| | |
| | else: |
| | edata += A |
| | done = True |
| |
|
| | if not done: |
| | if len(edge.Vertexes) == 1 and iscircle: |
| | |
| | svg = get_circle(plane, fill, stroke, linewidth, lstyle, edge) |
| | |
| | |
| | return "svg", svg |
| | elif len(edge.Vertexes) == 1 and isellipse: |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | _diff = (center.LastParameter - center.FirstParameter) / 2.0 |
| | endpoints = [get_proj(center.value(_diff), plane), get_proj(verts[-1].Point, plane)] |
| | else: |
| | endpoints = [get_proj(verts[-1].Point, plane)] |
| |
|
| | |
| | if iscircle: |
| | rx = ry = center.Radius |
| | rot = 0 |
| | else: |
| | rx = center.MajorRadius |
| | ry = center.MinorRadius |
| | _rot = center.AngleXU * center.Axis * App.Vector(0, 0, 1) |
| | rot = math.degrees(_rot) |
| | if rot > 90: |
| | rot -= 180 |
| | if rot < -90: |
| | rot += 180 |
| |
|
| | |
| | _diff = edge.ParameterRange[1] - edge.ParameterRange[0] |
| | _diff = _diff / math.pi |
| | flag_large_arc = (_diff % 2) > 1 |
| |
|
| | |
| | |
| | |
| |
|
| | |
| | |
| | _diff = edge.LastParameter - edge.FirstParameter |
| | t1 = edge.tangentAt(edge.FirstParameter) |
| | t2 = edge.tangentAt(edge.FirstParameter + _diff / 10) |
| | flag_sweep = DraftVecUtils.angle(t1, t2, drawing_plane_normal) < 0 |
| |
|
| | for v in endpoints: |
| | edata += ( |
| | "A {} {} {} " |
| | "{} {} " |
| | "{} {} ".format(rx, ry, rot, int(flag_large_arc), int(flag_sweep), v.x, v.y) |
| | ) |
| |
|
| | return "edata", edata |
| |
|
| |
|
| | def _get_path_bspline(plane, edge, edata): |
| | """Convert the edge to a BSpline and discretize it.""" |
| | bspline = edge.Curve.toBSpline(edge.FirstParameter, edge.LastParameter) |
| | if bspline.Degree > 3 or bspline.isRational(): |
| | try: |
| | bspline = bspline.approximateBSpline(0.05, 50, 3, "C0") |
| | except RuntimeError: |
| | _wrn("Debug: unable to approximate bspline from edge") |
| |
|
| | if bspline.Degree <= 3 and not bspline.isRational(): |
| | for bezierseg in bspline.toBezier(): |
| | if bezierseg.Degree > 3: |
| | _wrn("Bezier segment of degree > 3") |
| | raise AssertionError |
| | elif bezierseg.Degree == 1: |
| | edata += "L " |
| | elif bezierseg.Degree == 2: |
| | edata += "Q " |
| | elif bezierseg.Degree == 3: |
| | edata += "C " |
| |
|
| | for pole in bezierseg.getPoles()[1:]: |
| | v = get_proj(pole, plane) |
| | edata += "{} {} ".format(v.x, v.y) |
| | else: |
| | _msg( |
| | "Debug: one edge (hash {}) " |
| | "has been discretized " |
| | "with parameter 0.1".format(edge.hashCode()) |
| | ) |
| |
|
| | for linepoint in bspline.discretize(0.1)[1:]: |
| | v = get_proj(linepoint, plane) |
| | edata += "L {} {} ".format(v.x, v.y) |
| |
|
| | return edata |
| |
|
| |
|
| | def get_circle(plane, fill, stroke, linewidth, lstyle, edge): |
| | """Get the SVG representation from a circular edge.""" |
| | cen = get_proj(edge.Curve.Center, plane) |
| | rad = edge.Curve.Radius |
| |
|
| | if plane: |
| | drawing_plane_normal = plane.axis |
| | else: |
| | drawing_plane_normal = WorkingPlane.get_working_plane(update=False).axis |
| |
|
| | if round(edge.Curve.Axis.getAngle(drawing_plane_normal), 2) in [0, 3.14]: |
| | |
| | svg = "<circle " |
| | svg += 'cx="{}" cy="{}" r="{}" '.format(cen.x, cen.y, rad) |
| | else: |
| | |
| | svg = '<path d="{}" '.format(get_discretized(edge, plane)) |
| |
|
| | svg += 'stroke="{}" '.format(stroke) |
| | |
| | |
| | svg += 'stroke-width="{} px" '.format(linewidth) |
| | svg += 'style="' |
| | svg += "stroke-width:{};".format(linewidth) |
| | svg += "stroke-miterlimit:4;" |
| | svg += "stroke-dasharray:{};".format(lstyle) |
| | svg += "stroke-linecap:square;" |
| | svg += "fill:{}".format(fill) + '"' |
| | svg += "/>\n" |
| | return svg |
| |
|
| |
|
| | def getCircle(plane, fill, stroke, linewidth, lstyle, edge): |
| | """Get the SVG representation from a circular edge.""" |
| | utils.use_instead("get_circle") |
| | return get_circle(plane, fill, stroke, linewidth, lstyle, edge) |
| |
|
| |
|
| | def get_ellipse(plane, fill, stroke, linewidth, lstyle, edge): |
| | """Get the SVG representation from an elliptical edge.""" |
| | cen = get_proj(edge.Curve.Center, plane) |
| | mir = edge.Curve.MinorRadius |
| | mar = edge.Curve.MajorRadius |
| | svg = "<ellipse " |
| | svg += 'cx="{}" cy="{}" '.format(cen.x, cen.y) |
| | svg += 'rx="{}" ry="{}" '.format(mar, mir) |
| | svg += 'stroke="{}" '.format(stroke) |
| | svg += 'stroke-width="{} px" '.format(linewidth) |
| | svg += 'style="' |
| | svg += "stroke-width:{};".format(linewidth) |
| | svg += "stroke-miterlimit:4;" |
| | svg += "stroke-dasharray:{};".format(lstyle) |
| | svg += "stroke-linecap:square;" |
| | svg += "fill:{}".format(fill) + '"' |
| | svg += "/>\n" |
| | return svg |
| |
|
| |
|
| | def getEllipse(plane, fill, stroke, linewidth, lstyle, edge): |
| | """Get the SVG representation from an elliptical edge. DEPRECATED.""" |
| | utils.use_instead("get_ellipse") |
| | return get_ellipse(plane, fill, stroke, linewidth, lstyle, edge) |
| |
|
| |
|
| | def get_path( |
| | obj, |
| | plane, |
| | fill, |
| | pathdata, |
| | stroke, |
| | linewidth, |
| | lstyle, |
| | fill_opacity=None, |
| | edges=[], |
| | wires=[], |
| | pathname=None, |
| | ): |
| | """Get the SVG representation from an object's edges or wires. |
| | |
| | TODO: the `edges` and `wires` must not default to empty list `[]` |
| | but to `None`. Verify that the code doesn't break with this change. |
| | |
| | `edges` and `wires` are mutually exclusive. If no `wires` are provided, |
| | sort the `edges`, and use them. If `wires` are provided, sort the edges |
| | in these `wires`, and use them. |
| | """ |
| | svg = "<path " |
| |
|
| | if pathname is None: |
| | svg += 'id="{}" '.format(obj.Name) |
| | elif pathname != "": |
| | svg += 'id="{}" '.format(pathname) |
| |
|
| | svg += ' d="' |
| |
|
| | if not wires: |
| | egroups = Part.sortEdges(edges) |
| | else: |
| | egroups = [] |
| | first = True |
| | for w in wires: |
| | wire = w.copy() |
| | if first: |
| | first = False |
| | else: |
| | |
| | wire = DraftGeomUtils.invert(wire) |
| |
|
| | wire.fixWire() |
| | egroups.append(Part.__sortEdges__(wire.Edges)) |
| |
|
| | for _edges in egroups: |
| | edata = "" |
| |
|
| | for edgeindex, edge in enumerate(_edges): |
| | if edgeindex == 0: |
| | verts = edge.Vertexes |
| | if len(_edges) > 1: |
| | last_pt = verts[-1].Point |
| | nextverts = _edges[1].Vertexes |
| | if (last_pt - nextverts[0].Point).Length > 1e-6 and ( |
| | last_pt - nextverts[-1].Point |
| | ).Length > 1e-6: |
| | verts.reverse() |
| | v = get_proj(verts[0].Point, plane) |
| | edata += "M {} {} ".format(v.x, v.y) |
| | else: |
| | previousverts = verts |
| | verts = edge.Vertexes |
| | if (verts[0].Point - previousverts[-1].Point).Length > 1e-6: |
| | verts.reverse() |
| | if (verts[0].Point - previousverts[-1].Point).Length > 1e-6: |
| | raise ValueError("edges not ordered") |
| |
|
| | iscircle = DraftGeomUtils.geomType(edge) == "Circle" |
| | isellipse = DraftGeomUtils.geomType(edge) == "Ellipse" |
| |
|
| | if iscircle or isellipse: |
| | _type, data = _get_path_circ_ellipse( |
| | plane, edge, verts, edata, iscircle, isellipse, fill, stroke, linewidth, lstyle |
| | ) |
| | if _type == "svg": |
| | |
| | return data |
| |
|
| | |
| | edata = data |
| | elif DraftGeomUtils.geomType(edge) == "Line": |
| | v = get_proj(verts[-1].Point, plane) |
| | edata += "L {} {} ".format(v.x, v.y) |
| | else: |
| | |
| | |
| | edata = _get_path_bspline(plane, edge, edata) |
| |
|
| | if fill != "none": |
| | edata += "Z " |
| |
|
| | if edata in pathdata: |
| | |
| | return "" |
| | else: |
| | svg += edata |
| | pathdata.append(edata) |
| |
|
| | svg += '" ' |
| | svg += 'stroke="{}" '.format(stroke) |
| | svg += 'stroke-width="{} px" '.format(linewidth) |
| | svg += 'style="' |
| | svg += "stroke-width:{};".format(linewidth) |
| | svg += "stroke-miterlimit:4;" |
| | svg += "stroke-dasharray:{};".format(lstyle) |
| | svg += "stroke-linecap:square;" |
| | svg += "fill:{};".format(fill) |
| | |
| | if fill_opacity is not None: |
| | svg += "fill-opacity:{};".format(fill_opacity) |
| |
|
| | svg += 'fill-rule: evenodd"' |
| | svg += "/>\n" |
| | return svg |
| |
|
| |
|
| | def getPath( |
| | obj, |
| | plane, |
| | fill, |
| | pathdata, |
| | stroke, |
| | linewidth, |
| | lstyle, |
| | fill_opacity, |
| | edges=[], |
| | wires=[], |
| | pathname=None, |
| | ): |
| | """Get the SVG representation from a path. DEPRECATED.""" |
| | utils.use_instead("get_path") |
| | return get_path( |
| | obj, |
| | plane, |
| | fill, |
| | pathdata, |
| | stroke, |
| | linewidth, |
| | lstyle, |
| | fill_opacity, |
| | edges=edges, |
| | wires=wires, |
| | pathname=pathname, |
| | ) |
| |
|
| |
|
| | |
| |
|