Added Feed Method for clearing polygon. Some minor correction to Geometry.plot()
This commit is contained in:
parent
edab5af431
commit
fe2b4c7478
|
@ -37,3 +37,4 @@ class LoudDict(dict):
|
|||
"""
|
||||
|
||||
self.callback = callback
|
||||
|
||||
|
|
|
@ -383,11 +383,15 @@ class FlatCAMDraw(QtCore.QObject):
|
|||
:param fcgeometry: FlatCAMGeometry
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
_ = iter(fcgeometry.solid_geometry)
|
||||
geometry = fcgeometry.solid_geometry
|
||||
except TypeError:
|
||||
geometry = [fcgeometry.solid_geometry]
|
||||
|
||||
if fcgeometry.solid_geometry is None:
|
||||
geometry = []
|
||||
else:
|
||||
try:
|
||||
_ = iter(fcgeometry.solid_geometry)
|
||||
geometry = fcgeometry.solid_geometry
|
||||
except TypeError:
|
||||
geometry = [fcgeometry.solid_geometry]
|
||||
|
||||
# Delete contents of editor.
|
||||
self.shape_buffer = []
|
||||
|
@ -650,6 +654,9 @@ class FlatCAMDraw(QtCore.QObject):
|
|||
self.app.log.debug("plot_all()")
|
||||
self.axes.cla()
|
||||
for shape in self.shape_buffer:
|
||||
if shape['geometry'] is None: # TODO: This shouldn't have happened
|
||||
continue
|
||||
|
||||
if shape['utility']:
|
||||
self.plot_shape(geometry=shape['geometry'], linespec='k--', linewidth=1)
|
||||
continue
|
||||
|
|
144
FlatCAMObj.py
144
FlatCAMObj.py
|
@ -567,6 +567,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
|
|||
"travelz": 0.1,
|
||||
"feedrate": 5.0,
|
||||
# "toolselection": ""
|
||||
"tooldia": 0.1
|
||||
})
|
||||
|
||||
# TODO: Document this.
|
||||
|
@ -613,6 +614,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
|
|||
"travelz": self.ui.travelz_entry,
|
||||
"feedrate": self.ui.feedrate_entry,
|
||||
# "toolselection": self.ui.tools_entry
|
||||
"tooldia": self.ui.tooldia_entry
|
||||
})
|
||||
|
||||
assert isinstance(self.ui, ExcellonObjectUI)
|
||||
|
@ -620,13 +622,54 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
|
|||
self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
|
||||
# self.ui.choose_tools_button.clicked.connect(self.show_tool_chooser)
|
||||
self.ui.generate_cnc_button.clicked.connect(self.on_create_cncjob_button_click)
|
||||
self.ui.generate_milling_button.clicked.connect(self.on_generate_milling_button_click)
|
||||
|
||||
def get_selected_tools_list(self):
|
||||
"""
|
||||
Returns the keys to the self.tools dictionary corresponding
|
||||
to the selections on the tool list in the GUI.
|
||||
"""
|
||||
return [str(x.text()) for x in self.ui.tools_table.selectedItems()]
|
||||
|
||||
def on_generate_milling_button_click(self, *args):
|
||||
self.app.report_usage("excellon_on_create_milling_button")
|
||||
self.read_form()
|
||||
|
||||
# Get the tools from the list
|
||||
tools = self.get_selected_tools_list()
|
||||
|
||||
if len(tools) == 0:
|
||||
self.app.inform.emit("Please select one or more tools from the list and try again.")
|
||||
return
|
||||
|
||||
geo_name = self.options["name"] + "_mill"
|
||||
|
||||
def geo_init(geo_obj, app_obj):
|
||||
assert isinstance(geo_obj, FlatCAMGeometry)
|
||||
app_obj.progress.emit(20)
|
||||
|
||||
geo_obj.solid_geometry = []
|
||||
|
||||
for hole in self.drills:
|
||||
if hole['tool'] in tools:
|
||||
geo_obj.solid_geometry.append(
|
||||
Point(hole['point']).buffer(self.tools[hole['tool']]["C"]/2 - self.options["tooldia"]/2).exterior
|
||||
)
|
||||
|
||||
def geo_thread(app_obj):
|
||||
app_obj.new_object("geometry", geo_name, geo_init)
|
||||
app_obj.progress.emit(100)
|
||||
|
||||
# Send to worker
|
||||
# self.app.worker.add_task(job_thread, [self.app])
|
||||
self.app.worker_task.emit({'fcn': geo_thread, 'params': [self.app]})
|
||||
|
||||
def on_create_cncjob_button_click(self, *args):
|
||||
self.app.report_usage("excellon_on_create_cncjob_button")
|
||||
self.read_form()
|
||||
|
||||
# Get the tools from the list
|
||||
tools = [str(x.text()) for x in self.ui.tools_table.selectedItems()]
|
||||
tools = self.get_selected_tools_list()
|
||||
|
||||
if len(tools) == 0:
|
||||
self.app.inform.emit("Please select one or more tools from the list and try again.")
|
||||
|
@ -890,7 +933,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
|||
"cnctooldia": 0.4 / 25.4,
|
||||
"painttooldia": 0.0625,
|
||||
"paintoverlap": 0.15,
|
||||
"paintmargin": 0.01
|
||||
"paintmargin": 0.01,
|
||||
"paintmethod": "standard"
|
||||
})
|
||||
|
||||
# Attributes to be included in serialization
|
||||
|
@ -918,7 +962,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
|||
"cnctooldia": self.ui.cnctooldia_entry,
|
||||
"painttooldia": self.ui.painttooldia_entry,
|
||||
"paintoverlap": self.ui.paintoverlap_entry,
|
||||
"paintmargin": self.ui.paintmargin_entry
|
||||
"paintmargin": self.ui.paintmargin_entry,
|
||||
"paintmethod": self.ui.paintmethod_combo
|
||||
})
|
||||
|
||||
self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
|
||||
|
@ -945,13 +990,18 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
|||
subscription = self.app.plotcanvas.mpl_connect('button_press_event', doit)
|
||||
|
||||
def paint_poly(self, inside_pt, tooldia, overlap):
|
||||
|
||||
# Which polygon.
|
||||
poly = find_polygon(self.solid_geometry, inside_pt)
|
||||
|
||||
# Initializes the new geometry object
|
||||
def gen_paintarea(geo_obj, app_obj):
|
||||
assert isinstance(geo_obj, FlatCAMGeometry)
|
||||
#assert isinstance(app_obj, App)
|
||||
cp = clear_poly(poly.buffer(-self.options["paintmargin"]), tooldia, overlap)
|
||||
#cp = clear_poly(poly.buffer(-self.options["paintmargin"]), tooldia, overlap)
|
||||
cp = self.clear_polygon(poly.buffer(-self.options["paintmargin"]), tooldia, overlap=overlap)
|
||||
if self.options["paintmethod"] == "seed":
|
||||
cp = self.clear_polygon2(poly.buffer(-self.options["paintmargin"]), tooldia, overlap=overlap)
|
||||
geo_obj.solid_geometry = cp
|
||||
geo_obj.options["cnctooldia"] = tooldia
|
||||
|
||||
|
@ -1067,6 +1117,26 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
|||
|
||||
return factor
|
||||
|
||||
def plot_element(self, element):
|
||||
try:
|
||||
for sub_el in element:
|
||||
self.plot_element(sub_el)
|
||||
except TypeError:
|
||||
if type(element) == Polygon:
|
||||
x, y = element.exterior.coords.xy
|
||||
self.axes.plot(x, y, 'r-')
|
||||
for ints in element.interiors:
|
||||
x, y = ints.coords.xy
|
||||
self.axes.plot(x, y, 'r-')
|
||||
return
|
||||
|
||||
if type(element) == LineString or type(element) == LinearRing:
|
||||
x, y = element.coords.xy
|
||||
self.axes.plot(x, y, 'r-')
|
||||
return
|
||||
|
||||
FlatCAMApp.App.log.warning("Did not plot:", str(type(element)))
|
||||
|
||||
def plot(self):
|
||||
"""
|
||||
Plots the object into its axes. If None, of if the axes
|
||||
|
@ -1082,39 +1152,41 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
|||
|
||||
# Make sure solid_geometry is iterable.
|
||||
# TODO: This method should not modify the object !!!
|
||||
try:
|
||||
_ = iter(self.solid_geometry)
|
||||
except TypeError:
|
||||
if self.solid_geometry is None:
|
||||
self.solid_geometry = []
|
||||
else:
|
||||
self.solid_geometry = [self.solid_geometry]
|
||||
# try:
|
||||
# _ = iter(self.solid_geometry)
|
||||
# except TypeError:
|
||||
# if self.solid_geometry is None:
|
||||
# self.solid_geometry = []
|
||||
# else:
|
||||
# self.solid_geometry = [self.solid_geometry]
|
||||
#
|
||||
# for geo in self.solid_geometry:
|
||||
#
|
||||
# if type(geo) == Polygon:
|
||||
# x, y = geo.exterior.coords.xy
|
||||
# self.axes.plot(x, y, 'r-')
|
||||
# for ints in geo.interiors:
|
||||
# x, y = ints.coords.xy
|
||||
# self.axes.plot(x, y, 'r-')
|
||||
# continue
|
||||
#
|
||||
# if type(geo) == LineString or type(geo) == LinearRing:
|
||||
# x, y = geo.coords.xy
|
||||
# self.axes.plot(x, y, 'r-')
|
||||
# continue
|
||||
#
|
||||
# if type(geo) == MultiPolygon:
|
||||
# for poly in geo:
|
||||
# x, y = poly.exterior.coords.xy
|
||||
# self.axes.plot(x, y, 'r-')
|
||||
# for ints in poly.interiors:
|
||||
# x, y = ints.coords.xy
|
||||
# self.axes.plot(x, y, 'r-')
|
||||
# continue
|
||||
#
|
||||
# FlatCAMApp.App.log.warning("Did not plot:", str(type(geo)))
|
||||
|
||||
for geo in self.solid_geometry:
|
||||
|
||||
if type(geo) == Polygon:
|
||||
x, y = geo.exterior.coords.xy
|
||||
self.axes.plot(x, y, 'r-')
|
||||
for ints in geo.interiors:
|
||||
x, y = ints.coords.xy
|
||||
self.axes.plot(x, y, 'r-')
|
||||
continue
|
||||
|
||||
if type(geo) == LineString or type(geo) == LinearRing:
|
||||
x, y = geo.coords.xy
|
||||
self.axes.plot(x, y, 'r-')
|
||||
continue
|
||||
|
||||
if type(geo) == MultiPolygon:
|
||||
for poly in geo:
|
||||
x, y = poly.exterior.coords.xy
|
||||
self.axes.plot(x, y, 'r-')
|
||||
for ints in poly.interiors:
|
||||
x, y = ints.coords.xy
|
||||
self.axes.plot(x, y, 'r-')
|
||||
continue
|
||||
|
||||
FlatCAMApp.App.log.warning("Did not plot:", str(type(geo)))
|
||||
self.plot_element(self.solid_geometry)
|
||||
|
||||
self.app.plotcanvas.auto_adjust_axes()
|
||||
# GLib.idle_add(self.app.plotcanvas.auto_adjust_axes)
|
||||
|
|
47
ObjectUI.py
47
ObjectUI.py
|
@ -245,7 +245,9 @@ class GeometryObjectUI(ObjectUI):
|
|||
)
|
||||
self.custom_box.addWidget(self.generate_cnc_button)
|
||||
|
||||
## Paint area
|
||||
################
|
||||
## Paint area ##
|
||||
################
|
||||
self.paint_label = QtGui.QLabel('<b>Paint Area:</b>')
|
||||
self.paint_label.setToolTip(
|
||||
"Creates tool paths to cover the\n"
|
||||
|
@ -288,7 +290,19 @@ class GeometryObjectUI(ObjectUI):
|
|||
)
|
||||
grid2.addWidget(marginlabel, 2, 0)
|
||||
self.paintmargin_entry = LengthEntry()
|
||||
grid2.addWidget(self.paintmargin_entry)
|
||||
grid2.addWidget(self.paintmargin_entry, 2, 1)
|
||||
|
||||
# Method
|
||||
methodlabel = QtGui.QLabel('Method:')
|
||||
methodlabel.setToolTip(
|
||||
"Algorithm to paint the polygon."
|
||||
)
|
||||
grid2.addWidget(methodlabel, 3, 0)
|
||||
self.paintmethod_combo = RadioSet([
|
||||
{"label": "Standard", "value": "standard"},
|
||||
{"label": "Seed-based", "value": "seed"}
|
||||
])
|
||||
grid2.addWidget(self.paintmethod_combo, 3, 1)
|
||||
|
||||
# GO Button
|
||||
self.generate_paint_button = QtGui.QPushButton('Generate')
|
||||
|
@ -386,6 +400,35 @@ class ExcellonObjectUI(ObjectUI):
|
|||
)
|
||||
self.custom_box.addWidget(self.generate_cnc_button)
|
||||
|
||||
## Milling Holes
|
||||
self.mill_hole_label = QtGui.QLabel('<b>Mill Holes</b>')
|
||||
self.mill_hole_label.setToolTip(
|
||||
"Create Geometry for milling holes."
|
||||
)
|
||||
self.custom_box.addWidget(self.mill_hole_label)
|
||||
|
||||
grid1 = QtGui.QGridLayout()
|
||||
self.custom_box.addLayout(grid1)
|
||||
tdlabel = QtGui.QLabel('Tool dia:')
|
||||
tdlabel.setToolTip(
|
||||
"Diameter of the cutting tool."
|
||||
)
|
||||
grid1.addWidget(tdlabel, 0, 0)
|
||||
self.tooldia_entry = LengthEntry()
|
||||
grid1.addWidget(self.tooldia_entry, 0, 1)
|
||||
|
||||
choose_tools_label2 = QtGui.QLabel(
|
||||
"Select from the tools section above\n"
|
||||
"the tools you want to include."
|
||||
)
|
||||
self.custom_box.addWidget(choose_tools_label2)
|
||||
|
||||
self.generate_milling_button = QtGui.QPushButton('Generate Geometry')
|
||||
self.generate_milling_button.setToolTip(
|
||||
"Create the Geometry Object\n"
|
||||
"for milling toolpaths."
|
||||
)
|
||||
self.custom_box.addWidget(self.generate_milling_button)
|
||||
|
||||
class GerberObjectUI(ObjectUI):
|
||||
"""
|
||||
|
|
136
camlib.py
136
camlib.py
|
@ -216,6 +216,14 @@ class Geometry(object):
|
|||
"""
|
||||
Creates geometry inside a polygon for a tool to cover
|
||||
the whole area.
|
||||
|
||||
This algorithm shrinks the edges of the polygon and takes
|
||||
the resulting edges as toolpaths.
|
||||
|
||||
:param polygon: Polygon to clear.
|
||||
:param tooldia: Diameter of the tool.
|
||||
:param overlap: Overlap of toolpasses.
|
||||
:return:
|
||||
"""
|
||||
poly_cuts = [polygon.buffer(-tooldia/2.0)]
|
||||
while True:
|
||||
|
@ -226,6 +234,58 @@ class Geometry(object):
|
|||
break
|
||||
return poly_cuts
|
||||
|
||||
def clear_polygon2(self, polygon, tooldia, seedpoint=None, overlap=0.15):
|
||||
"""
|
||||
Creates geometry inside a polygon for a tool to cover
|
||||
the whole area.
|
||||
|
||||
This algorithm starts with a seed point inside the polygon
|
||||
and draws circles around it. Arcs inside the polygons are
|
||||
valid cuts. Finalizes by cutting around the inside edge of
|
||||
the polygon.
|
||||
|
||||
:param polygon:
|
||||
:param tooldia:
|
||||
:param seedpoint:
|
||||
:param overlap:
|
||||
:return:
|
||||
"""
|
||||
|
||||
if seedpoint is None:
|
||||
seedpoint = polygon.representative_point()
|
||||
|
||||
# Current buffer radius
|
||||
radius = tooldia/2*(1-overlap)
|
||||
|
||||
# The toolpaths
|
||||
geoms = [Point(seedpoint).buffer(radius).exterior]
|
||||
|
||||
# Path margin
|
||||
path_margin = polygon.buffer(-tooldia/2)
|
||||
|
||||
# Grow from seed until outside the box.
|
||||
while 1:
|
||||
path = Point(seedpoint).buffer(radius).exterior
|
||||
path = path.intersection(path_margin)
|
||||
|
||||
# Touches polygon?
|
||||
if path.is_empty:
|
||||
break
|
||||
else:
|
||||
geoms.append(path)
|
||||
|
||||
radius += tooldia*(1-overlap)
|
||||
|
||||
# Clean edges
|
||||
outer_edges = [x.exterior for x in autolist(polygon.buffer(-tooldia/2))]
|
||||
inner_edges = []
|
||||
for x in autolist(polygon.buffer(-tooldia/2)): # Over resulting polygons
|
||||
for y in x.interiors: # Over interiors of each polygon
|
||||
inner_edges.append(y)
|
||||
geoms += outer_edges + inner_edges
|
||||
|
||||
return geoms
|
||||
|
||||
def scale(self, factor):
|
||||
"""
|
||||
Scales all of the object's geometry by a given factor. Override
|
||||
|
@ -2695,30 +2755,30 @@ def arc_angle(start, stop, direction):
|
|||
return angle
|
||||
|
||||
|
||||
def clear_poly(poly, tooldia, overlap=0.1):
|
||||
"""
|
||||
Creates a list of Shapely geometry objects covering the inside
|
||||
of a Shapely.Polygon. Use for removing all the copper in a region
|
||||
or bed flattening.
|
||||
|
||||
:param poly: Target polygon
|
||||
:type poly: Shapely.Polygon
|
||||
:param tooldia: Diameter of the tool
|
||||
:type tooldia: float
|
||||
:param overlap: Fraction of the tool diameter to overlap
|
||||
in each pass.
|
||||
:type overlap: float
|
||||
:return: list of Shapely.Polygon
|
||||
:rtype: list
|
||||
"""
|
||||
poly_cuts = [poly.buffer(-tooldia/2.0)]
|
||||
while True:
|
||||
poly = poly_cuts[-1].buffer(-tooldia*(1-overlap))
|
||||
if poly.area > 0:
|
||||
poly_cuts.append(poly)
|
||||
else:
|
||||
break
|
||||
return poly_cuts
|
||||
# def clear_poly(poly, tooldia, overlap=0.1):
|
||||
# """
|
||||
# Creates a list of Shapely geometry objects covering the inside
|
||||
# of a Shapely.Polygon. Use for removing all the copper in a region
|
||||
# or bed flattening.
|
||||
#
|
||||
# :param poly: Target polygon
|
||||
# :type poly: Shapely.Polygon
|
||||
# :param tooldia: Diameter of the tool
|
||||
# :type tooldia: float
|
||||
# :param overlap: Fraction of the tool diameter to overlap
|
||||
# in each pass.
|
||||
# :type overlap: float
|
||||
# :return: list of Shapely.Polygon
|
||||
# :rtype: list
|
||||
# """
|
||||
# poly_cuts = [poly.buffer(-tooldia/2.0)]
|
||||
# while True:
|
||||
# poly = poly_cuts[-1].buffer(-tooldia*(1-overlap))
|
||||
# if poly.area > 0:
|
||||
# poly_cuts.append(poly)
|
||||
# else:
|
||||
# break
|
||||
# return poly_cuts
|
||||
|
||||
|
||||
def find_polygon(poly_set, point):
|
||||
|
@ -2775,7 +2835,7 @@ def dict2obj(d):
|
|||
return d
|
||||
|
||||
|
||||
def plotg(geo):
|
||||
def plotg(geo, solid_poly=False):
|
||||
try:
|
||||
_ = iter(geo)
|
||||
except:
|
||||
|
@ -2783,12 +2843,21 @@ def plotg(geo):
|
|||
|
||||
for g in geo:
|
||||
if type(g) == Polygon:
|
||||
x, y = g.exterior.coords.xy
|
||||
plot(x, y)
|
||||
for ints in g.interiors:
|
||||
x, y = ints.coords.xy
|
||||
if solid_poly:
|
||||
patch = PolygonPatch(g,
|
||||
facecolor="#BBF268",
|
||||
edgecolor="#006E20",
|
||||
alpha=0.75,
|
||||
zorder=2)
|
||||
ax = subplot(111)
|
||||
ax.add_patch(patch)
|
||||
else:
|
||||
x, y = g.exterior.coords.xy
|
||||
plot(x, y)
|
||||
continue
|
||||
for ints in g.interiors:
|
||||
x, y = ints.coords.xy
|
||||
plot(x, y)
|
||||
continue
|
||||
|
||||
if type(g) == LineString or type(g) == LinearRing:
|
||||
x, y = g.coords.xy
|
||||
|
@ -3025,3 +3094,10 @@ class Zprofile:
|
|||
return [{"path": path.intersection(self.polygons[i]),
|
||||
"z": self.data[i][2]} for i in crossing]
|
||||
|
||||
|
||||
def autolist(obj):
|
||||
try:
|
||||
_ = iter(obj)
|
||||
return obj
|
||||
except TypeError:
|
||||
return [obj]
|
Loading…
Reference in New Issue