You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
160 lines
5.6 KiB
160 lines
5.6 KiB
import FreeCAD |
|
import FreeCAD as App |
|
import FreeCADGui as Gui |
|
import numpy as np |
|
import math |
|
import Part |
|
from shapely.geometry import Polygon, Point |
|
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: |
|
projection_result = [] |
|
for e in c.Edges: |
|
projection_result.append(face.makeParallelProjection(e, App.Vector(0, 0, 1))) |
|
# proj.add(Part.makeCompound(projection_result)) |
|
proj.append(projection_result) |
|
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[Polygon]) -> List[np.ndarray]: |
|
""" |
|
Returns the list of paths and coordinates from a cross-section (i.e. Trimesh Path2D). This is required to be |
|
done for performing boolean operations and offsetting with the internal PyClipper package. |
|
:param shapes: A list of :class:`shapely.geometry.Polygon` 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 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("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_coords_from_shape(face): |
|
outerpoly = get_polygon_from_subshape(face.OuterWire) |
|
inner_polys = [] |
|
for inner_wire in face.SubShapes[1:]: |
|
tmp = get_polygon_from_subshape(inner_wire) |
|
inner_polys.append(tmp) |
|
poly = Polygon(outerpoly, holes=inner_polys) |
|
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): |
|
contours = [] |
|
for geom in geoms: |
|
if geom.type() == LayerGeometryType.Polygon: |
|
# print("Contour with {} coords".format(len(geom.coords))) |
|
coords = rdp.rdp(geom.coords, epsilon=0.3, 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
|
|
|