|
|
|
|
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
|