import FreeCAD import FreeCAD as App import FreeCADGui as Gui import numpy as np import math import Part import Draft import shapely from shapely.geometry import Polygon, Point from shapely.validation import make_valid, explain_validity from pyslm import hatching as hatching from pyslm.geometry.geometry import LayerGeometryType from typing import List import rdp def project_to_face(compounds, face): # proj = Part.makeCompound([]) proj = [] for c in compounds: shape = face.makeParallelProjection(c, App.Vector(0, 0, 1)) proj.append(shape) return proj def map_wire(wire, surface): """Map wire on target surface Input wire must be on XY plane""" plane = Part.Plane().toShape() mapped_edges = [] for e in wire.Edges: c, fp, lp = plane.curveOnSurface(e) mapped_edges.append(c.toShape(surface, fp, lp)) return Part.Wire(mapped_edges) def path2DToPathList(shapes: List[shapely.geometry.polygon.Polygon]) -> List[np.ndarray]: """ Returns the list of paths and coordinates from a cross-section (i.e. :class:`Trimesh.path.Path2D` objects). This is required to be done for performing boolean operations and offsetting with the internal PyClipper package. :param shapes: A list of Shapely Polygons representing a cross-section or container of closed polygons :return: A list of paths (Numpy Coordinate Arrays) describing fully closed and oriented paths. """ paths = [] for poly in shapes: coords = np.array(poly.exterior.coords) paths.append(coords) for path in poly.interiors: coords = np.array(path.coords) paths.append(coords) return paths def get_polypoints_from_edges(edges): # print("START NEW POLYGON") poly_points = [] # print("edges:", len(edges)) for edge in Part.__sortEdges__(edges): if type(edge.Curve) is Part.Circle and edge.Closed: # print("Edge is Circle") c = edge.Curve center = Point(c.Center.x, c.Center.y) radius = c.Radius circle = center.buffer(radius) poly_points = circle.exterior.coords elif type(edge.Curve) in [Part.Ellipse, Part.BSplineCurve, Part.Circle]: # print("Edge is Ellipse, BSpline or unclosed Circle") n = math.floor(edge.Length/2.3) if n > 200: n = 200 if edge.Closed: # print("Edge is closed Ellipse or BSpline") for v in edge.discretize(Number=n): poly_points.append((v.x, v.y)) else: # print("Edge is unclosed Circle") # for Circles at least 64 steps for full circle for v in edge.discretize(Number=n, First=edge.FirstParameter, Last=edge.LastParameter): poly_points.append((v.x, v.y)) else: # print("Line Vertexes:", len(edge.Vertexes)) # print("Line Vertexes:", [(v.Point.x, v.Point.y) for v in edge.Vertexes]) for v in edge.Vertexes: poly_points.append((v.Point.x, v.Point.y)) u = np.unique(poly_points, axis=0) # print("UNIQUE:", u) # print("END NEW POLYGON") return poly_points def tuple_is_equal(t1, t2): if math.isclose(t1[0], t2[0]) and math.isclose(t1[1], t2[1]): return True return False def get_polygon_from_subshape(subshape): # print("START NEW POLYGON") polygon = [] for edge in subshape.Edges: # Part.__sortEdges__(subshape.Edges): poly_points = [] if type(edge.Curve) in [Part.Ellipse, Part.BSplineCurve, Part.Circle]: n = math.floor(edge.Length/2.3) for v in edge.discretize(Number=n, First=edge.FirstParameter, Last=edge.LastParameter): poly_points.append((v.x, v.y)) else: # print("Some polygon or rect:") # print("Num Line Vertexes:", len(edge.Vertexes)) # print("Line Vertexes:", [(v.Point.x, v.Point.y) for v in edge.Vertexes]) for v in edge.Vertexes: poly_points.append((v.Point.x, v.Point.y)) if len(polygon): if tuple_is_equal(poly_points[0], polygon[-1]): polygon.extend(poly_points[1:]) else: polygon.extend(poly_points) else: polygon.extend(poly_points) # print("END NEW POLYGON") # return LinearRing(polygon) return polygon def get_polygon_from_wire(wire, discretization_factor): polygon = [] n = math.floor(wire.Length/discretization_factor) for v in wire.discretize(Number=n)[:-1]: polygon.append((v.x, v.y)) return polygon def get_coords_from_shape(face, rdp_epsilon, discretization_factor): #outerpoly = get_polygon_from_subshape(face.OuterWire) #sv0 = Draft.make_shape2dview(face.OuterWire, FreeCAD.Vector(0.0, 0.0, 1.0)) outerpoly = get_polygon_from_wire(face.OuterWire, discretization_factor) inner_polys = [] subshapes = [x for x in face.SubShapes if not x.isEqual(face.OuterWire)] for inner_wire in subshapes: tmp = get_polygon_from_wire(inner_wire, discretization_factor) inner_polys.append(tmp) poly = Polygon(shell=outerpoly, holes=inner_polys) print("Polygon: ", poly) valid_poly = make_valid(poly) print("Validated Polygon:", explain_validity(valid_poly)) return path2DToPathList([poly]) def create_hatch_lines(geoms): hatchlines = [] for geom in geoms: if geom.type() == LayerGeometryType.Hatch: # print("Hatch with {} coords".format(len(geom.coords))) hatches = np.vstack([geom.coords.reshape(-1, 2, 2)]) for line in hatches: p0 = line[0] p1 = line[1] pp = Part.makeLine(App.Vector(p0[0], p0[1],0), App.Vector(p1[0],p1[1],0)) hatchlines.append(pp) return hatchlines def create_contour_lines(geoms, epsilon=0.2): contours = [] for geom in geoms: if geom.type() == LayerGeometryType.Polygon: # print("Contour with {} coords".format(len(geom.coords))) coords = rdp.rdp(geom.coords, epsilon=epsilon, algo="iter", return_mask=False) #print("Simplfied Poly:", len(coords)) pp = Part.makePolygon([App.Vector(x,y,0) for (x,y) in coords]) contours.append(pp) return contours