Added feature: Select all polygons for painting and shell support with "paint" command.
This commit is contained in:
@@ -259,6 +259,7 @@ class App(QtCore.QObject):
|
||||
"geometry_spindlespeed": self.defaults_form.geometry_group.cncspindlespeed_entry,
|
||||
"geometry_paintoverlap": self.defaults_form.geometry_group.paintoverlap_entry,
|
||||
"geometry_paintmargin": self.defaults_form.geometry_group.paintmargin_entry,
|
||||
"geometry_selectmethod": self.defaults_form.geometry_group.selectmethod_combo,
|
||||
"cncjob_plot": self.defaults_form.cncjob_group.plot_cb,
|
||||
"cncjob_tooldia": self.defaults_form.cncjob_group.tooldia_entry,
|
||||
"cncjob_prepend": self.defaults_form.cncjob_group.prepend_text,
|
||||
@@ -304,6 +305,7 @@ class App(QtCore.QObject):
|
||||
"geometry_painttooldia": 0.07,
|
||||
"geometry_paintoverlap": 0.15,
|
||||
"geometry_paintmargin": 0.0,
|
||||
"geometry_selectmethod": "single",
|
||||
"cncjob_plot": True,
|
||||
"cncjob_tooldia": 0.016,
|
||||
"cncjob_prepend": "",
|
||||
@@ -397,6 +399,7 @@ class App(QtCore.QObject):
|
||||
"geometry_painttooldia": self.options_form.geometry_group.painttooldia_entry,
|
||||
"geometry_paintoverlap": self.options_form.geometry_group.paintoverlap_entry,
|
||||
"geometry_paintmargin": self.options_form.geometry_group.paintmargin_entry,
|
||||
"geometry_selectmethod": self.options_form.geometry_group.selectmethod_combo,
|
||||
"cncjob_plot": self.options_form.cncjob_group.plot_cb,
|
||||
"cncjob_tooldia": self.options_form.cncjob_group.tooldia_entry,
|
||||
"cncjob_prepend": self.options_form.cncjob_group.prepend_text,
|
||||
@@ -439,12 +442,15 @@ class App(QtCore.QObject):
|
||||
"geometry_painttooldia": 0.07,
|
||||
"geometry_paintoverlap": 0.15,
|
||||
"geometry_paintmargin": 0.0,
|
||||
"geometry_selectmethod": "single",
|
||||
"cncjob_plot": True,
|
||||
"cncjob_tooldia": 0.016,
|
||||
"cncjob_prepend": "",
|
||||
"cncjob_append": "",
|
||||
"background_timeout": 300000, #default value is 5 minutes
|
||||
"verbose_error_level": 0, # shell verbosity 0 = default(python trace only for unknown errors), 1 = show trace(show trace allways), 2 = (For the future).
|
||||
"background_timeout": 300000, # Default value is 5 minutes
|
||||
"verbose_error_level": 0, # Shell verbosity:
|
||||
# 0 = default(python trace only for unknown errors),
|
||||
# 1 = show trace(show trace allways), 2 = (For the future).
|
||||
})
|
||||
self.options.update(self.defaults) # Copy app defaults to project options
|
||||
#self.options_write_form()
|
||||
@@ -877,13 +883,13 @@ class App(QtCore.QObject):
|
||||
#self.shell.append_error("?\n")
|
||||
self.shell.append_error(str(e) + "\n")
|
||||
|
||||
def info(self, msg):
|
||||
def info(self, msg, toshell=True):
|
||||
"""
|
||||
Informs the user. Normally on the status bar, optionally
|
||||
also on the shell.
|
||||
|
||||
:param msg: Text to write.
|
||||
:param toshell: Forward the
|
||||
:param toshell: Forward the meesage to the shell.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
@@ -894,12 +900,15 @@ class App(QtCore.QObject):
|
||||
msg_ = match.group(2)
|
||||
self.ui.fcinfo.set_status(QtCore.QString(msg_), level=level)
|
||||
|
||||
error = level == "error" or level == "warning"
|
||||
self.shell_message(msg, error=error, show=True)
|
||||
if toshell:
|
||||
error = level == "error" or level == "warning"
|
||||
self.shell_message(msg, error=error, show=True)
|
||||
|
||||
else:
|
||||
self.ui.fcinfo.set_status(QtCore.QString(msg), level="info")
|
||||
self.shell_message(msg)
|
||||
|
||||
if toshell:
|
||||
self.shell_message(msg)
|
||||
|
||||
def load_defaults(self):
|
||||
"""
|
||||
@@ -981,6 +990,11 @@ class App(QtCore.QObject):
|
||||
this is, updates the GUI accordingly, any other records and plots it.
|
||||
This method is thread-safe.
|
||||
|
||||
Notes:
|
||||
* If the name is in use, the self.collection will modify it
|
||||
when appending it to the collection. There is no need to handle
|
||||
name conflicts here.
|
||||
|
||||
:param kind: The kind of object to create. One of 'gerber',
|
||||
'excellon', 'cncjob' and 'geometry'.
|
||||
:type kind: str
|
||||
@@ -998,19 +1012,6 @@ class App(QtCore.QObject):
|
||||
|
||||
t0 = time.time() # Debug
|
||||
|
||||
### Check for existing name
|
||||
# while name in self.collection.get_names():
|
||||
# ## Create a new name
|
||||
# # Ends with number?
|
||||
# App.log.debug("new_object(): Object name (%s) exists, changing." % name)
|
||||
# match = re.search(r'(.*[^\d])?(\d+)$', name)
|
||||
# if match: # Yes: Increment the number!
|
||||
# base = match.group(1) or ''
|
||||
# num = int(match.group(2))
|
||||
# name = base + str(num + 1)
|
||||
# else: # No: add a number!
|
||||
# name += "_1"
|
||||
|
||||
## Create object
|
||||
classdict = {
|
||||
"gerber": FlatCAMGerber,
|
||||
|
||||
@@ -746,7 +746,21 @@ class GeometryOptionsGroupUI(OptionsGroupUI):
|
||||
)
|
||||
grid2.addWidget(marginlabel, 2, 0)
|
||||
self.paintmargin_entry = LengthEntry()
|
||||
grid2.addWidget(self.paintmargin_entry)
|
||||
grid2.addWidget(self.paintmargin_entry, 2, 1)
|
||||
|
||||
# Polygon selection
|
||||
selectlabel = QtGui.QLabel('Selection:')
|
||||
selectlabel.setToolTip(
|
||||
"How to select the polygons to paint."
|
||||
)
|
||||
grid2.addWidget(selectlabel, 3, 0)
|
||||
# grid3 = QtGui.QGridLayout()
|
||||
self.selectmethod_combo = RadioSet([
|
||||
{"label": "Single", "value": "single"},
|
||||
{"label": "All", "value": "all"},
|
||||
# {"label": "Rectangle", "value": "rectangle"}
|
||||
])
|
||||
grid2.addWidget(self.selectmethod_combo, 3, 1)
|
||||
|
||||
|
||||
class CNCJobOptionsGroupUI(OptionsGroupUI):
|
||||
|
||||
111
FlatCAMObj.py
111
FlatCAMObj.py
@@ -1203,7 +1203,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
||||
"paintmargin": 0.01,
|
||||
"paintmethod": "standard",
|
||||
"multidepth": False,
|
||||
"depthperpass": 0.002
|
||||
"depthperpass": 0.002,
|
||||
"selectmethod": "single"
|
||||
})
|
||||
|
||||
# Attributes to be included in serialization
|
||||
@@ -1234,7 +1235,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
||||
"paintmargin": self.ui.paintmargin_entry,
|
||||
"paintmethod": self.ui.paintmethod_combo,
|
||||
"multidepth": self.ui.mpass_cb,
|
||||
"depthperpass": self.ui.maxdepth_entry
|
||||
"depthperpass": self.ui.maxdepth_entry,
|
||||
"selectmethod": self.ui.selectmethod_combo
|
||||
})
|
||||
|
||||
self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
|
||||
@@ -1244,24 +1246,38 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
||||
def on_paint_button_click(self, *args):
|
||||
self.app.report_usage("geometry_on_paint_button")
|
||||
|
||||
self.app.info("Click inside the desired polygon.")
|
||||
self.read_form()
|
||||
tooldia = self.options["painttooldia"]
|
||||
overlap = self.options["paintoverlap"]
|
||||
|
||||
# Connection ID for the click event
|
||||
subscription = None
|
||||
if self.options["selectmethod"] == "all":
|
||||
self.paint_poly_all(tooldia, overlap)
|
||||
return
|
||||
|
||||
# To be called after clicking on the plot.
|
||||
def doit(event):
|
||||
self.app.info("Painting polygon...")
|
||||
self.app.plotcanvas.mpl_disconnect(subscription)
|
||||
point = [event.xdata, event.ydata]
|
||||
self.paint_poly(point, tooldia, overlap)
|
||||
if self.options["selectmethod"] == "single":
|
||||
self.app.info("Click inside the desired polygon.")
|
||||
|
||||
subscription = self.app.plotcanvas.mpl_connect('button_press_event', doit)
|
||||
# To be called after clicking on the plot.
|
||||
def doit(event):
|
||||
self.app.info("Painting polygon...")
|
||||
self.app.plotcanvas.mpl_disconnect(subscription)
|
||||
point = [event.xdata, event.ydata]
|
||||
self.paint_poly_single_click(point, tooldia, overlap)
|
||||
|
||||
def paint_poly(self, inside_pt, tooldia, overlap):
|
||||
subscription = self.app.plotcanvas.mpl_connect('button_press_event', doit)
|
||||
|
||||
def paint_poly_single_click(self, inside_pt, tooldia, overlap, outname=None):
|
||||
"""
|
||||
Paints a polygon selected by clicking on its interior.
|
||||
|
||||
Note:
|
||||
* The margin is taken directly from the form.
|
||||
|
||||
:param inside_pt: [x, y]
|
||||
:param tooldia: Diameter of the painting tool
|
||||
:param overlap: Overlap of the tool between passes.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
# Which polygon.
|
||||
#poly = find_polygon(self.solid_geometry, inside_pt)
|
||||
@@ -1275,7 +1291,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
||||
|
||||
proc = self.app.proc_container.new("Painting polygon.")
|
||||
|
||||
name = self.options["name"] + "_paint"
|
||||
name = outname or self.options["name"] + "_paint"
|
||||
|
||||
# Initializes the new geometry object
|
||||
def gen_paintarea(geo_obj, app_obj):
|
||||
@@ -1293,6 +1309,73 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
||||
|
||||
geo_obj.solid_geometry = list(cp.get_objects())
|
||||
geo_obj.options["cnctooldia"] = tooldia
|
||||
|
||||
# Experimental...
|
||||
print "Indexing...",
|
||||
geo_obj.make_index()
|
||||
print "Done"
|
||||
|
||||
self.app.inform.emit("Done.")
|
||||
|
||||
def job_thread(app_obj):
|
||||
try:
|
||||
app_obj.new_object("geometry", name, gen_paintarea)
|
||||
except Exception as e:
|
||||
proc.done()
|
||||
raise e
|
||||
proc.done()
|
||||
|
||||
self.app.inform.emit("Polygon Paint started ...")
|
||||
|
||||
# Promise object with the new name
|
||||
self.app.collection.promise(name)
|
||||
|
||||
# Background
|
||||
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
|
||||
|
||||
def paint_poly_all(self, tooldia, overlap, outname=None):
|
||||
|
||||
proc = self.app.proc_container.new("Painting polygon.")
|
||||
|
||||
name = outname or self.options["name"] + "_paint"
|
||||
|
||||
def recurse(geo):
|
||||
try:
|
||||
for subg in geo:
|
||||
for subsubg in recurse(subg):
|
||||
yield subsubg
|
||||
except TypeError:
|
||||
if isinstance(geo, Polygon):
|
||||
yield geo
|
||||
|
||||
raise StopIteration
|
||||
|
||||
# Initializes the new geometry object
|
||||
def gen_paintarea(geo_obj, app_obj):
|
||||
assert isinstance(geo_obj, FlatCAMGeometry), \
|
||||
"Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
|
||||
|
||||
geo_obj.solid_geometry = []
|
||||
|
||||
for poly in recurse(self.solid_geometry):
|
||||
|
||||
if self.options["paintmethod"] == "seed":
|
||||
cp = self.clear_polygon2(poly.buffer(-self.options["paintmargin"]),
|
||||
tooldia, overlap=overlap)
|
||||
|
||||
else:
|
||||
cp = self.clear_polygon(poly.buffer(-self.options["paintmargin"]),
|
||||
tooldia, overlap=overlap)
|
||||
|
||||
geo_obj.solid_geometry += list(cp.get_objects())
|
||||
|
||||
geo_obj.options["cnctooldia"] = tooldia
|
||||
|
||||
# Experimental...
|
||||
print "Indexing...",
|
||||
geo_obj.make_index()
|
||||
print "Done"
|
||||
|
||||
self.app.inform.emit("Done.")
|
||||
|
||||
def job_thread(app_obj):
|
||||
|
||||
14
ObjectUI.py
14
ObjectUI.py
@@ -397,6 +397,20 @@ class GeometryObjectUI(ObjectUI):
|
||||
])
|
||||
grid2.addWidget(self.paintmethod_combo, 3, 1)
|
||||
|
||||
# Polygon selection
|
||||
selectlabel = QtGui.QLabel('Selection:')
|
||||
selectlabel.setToolTip(
|
||||
"How to select the polygons to paint."
|
||||
)
|
||||
grid2.addWidget(selectlabel, 4, 0)
|
||||
#grid3 = QtGui.QGridLayout()
|
||||
self.selectmethod_combo = RadioSet([
|
||||
{"label": "Single", "value": "single"},
|
||||
{"label": "All", "value": "all"},
|
||||
#{"label": "Rectangle", "value": "rectangle"}
|
||||
])
|
||||
grid2.addWidget(self.selectmethod_combo, 4, 1)
|
||||
|
||||
# GO Button
|
||||
self.generate_paint_button = QtGui.QPushButton('Generate')
|
||||
self.generate_paint_button.setToolTip(
|
||||
|
||||
10
camlib.py
10
camlib.py
@@ -92,6 +92,16 @@ class Geometry(object):
|
||||
# Flattened geometry (list of paths only)
|
||||
self.flat_geometry = []
|
||||
|
||||
# Index
|
||||
self.index = None
|
||||
|
||||
def make_index(self):
|
||||
self.flatten()
|
||||
self.index = FlatCAMRTree()
|
||||
|
||||
for i, g in enumerate(self.flat_geometry):
|
||||
self.index.insert(i, g)
|
||||
|
||||
def add_circle(self, origin, radius):
|
||||
"""
|
||||
Adds a circle to the object.
|
||||
|
||||
84
tclCommands/TclCommandPaint.py
Normal file
84
tclCommands/TclCommandPaint.py
Normal file
@@ -0,0 +1,84 @@
|
||||
from ObjectCollection import *
|
||||
import TclCommand
|
||||
|
||||
|
||||
class TclCommandPaint(TclCommand.TclCommandSignaled):
|
||||
"""
|
||||
Paint the interior of polygons
|
||||
"""
|
||||
|
||||
# Array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
|
||||
aliases = ['paint']
|
||||
|
||||
# dictionary of types from Tcl command, needs to be ordered
|
||||
arg_names = collections.OrderedDict([
|
||||
('name', str),
|
||||
('tooldia', float),
|
||||
('overlap', float)
|
||||
])
|
||||
|
||||
# dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value
|
||||
option_types = collections.OrderedDict([
|
||||
('outname', str),
|
||||
('all', bool),
|
||||
('x', float),
|
||||
('y', float)
|
||||
])
|
||||
|
||||
# array of mandatory options for current Tcl command: required = {'name','outname'}
|
||||
required = ['name', 'tooldia', 'overlap']
|
||||
|
||||
# structured help for current command, args needs to be ordered
|
||||
help = {
|
||||
'main': "Paint polygons",
|
||||
'args': collections.OrderedDict([
|
||||
('name', 'Name of the source Geometry object.'),
|
||||
('tooldia', 'Diameter of the tool to be used.'),
|
||||
('overlap', 'Fraction of the tool diameter to overlap cuts.'),
|
||||
('outname', 'Name of the resulting Geometry object.'),
|
||||
('all', 'Paint all polygons in the object.'),
|
||||
('x', 'X value of coordinate for the selection of a single polygon.'),
|
||||
('y', 'Y value of coordinate for the selection of a single polygon.')
|
||||
]),
|
||||
'examples': []
|
||||
}
|
||||
|
||||
def execute(self, args, unnamed_args):
|
||||
"""
|
||||
execute current TCL shell command
|
||||
|
||||
:param args: array of known named arguments and options
|
||||
:param unnamed_args: array of other values which were passed into command
|
||||
without -somename and we do not have them in known arg_names
|
||||
:return: None or exception
|
||||
"""
|
||||
|
||||
name = args['name']
|
||||
tooldia = args['tooldia']
|
||||
overlap = args['overlap']
|
||||
|
||||
if 'outname' in args:
|
||||
outname = args['outname']
|
||||
else:
|
||||
outname = name + "_paint"
|
||||
|
||||
obj = self.app.collection.get_by_name(name)
|
||||
if obj is None:
|
||||
self.raise_tcl_error("Object not found: %s" % name)
|
||||
|
||||
if not isinstance(obj, Geometry):
|
||||
self.raise_tcl_error('Expected Geometry, got %s %s.' % (name, type(obj)))
|
||||
|
||||
if 'all' in args and args['all']:
|
||||
obj.paint_poly_all(tooldia, overlap, outname)
|
||||
return
|
||||
|
||||
if 'x' not in args or 'y' not in args:
|
||||
self.raise_tcl_error('Expected -all 1 or -x <value> and -y <value>.')
|
||||
|
||||
x = args['x']
|
||||
y = args['y']
|
||||
|
||||
obj.paint_poly_single_click([x, y], tooldia, overlap, outname)
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import tclCommands.TclCommandInteriors
|
||||
import tclCommands.TclCommandIsolate
|
||||
import tclCommands.TclCommandNew
|
||||
import tclCommands.TclCommandOpenGerber
|
||||
import tclCommands.TclCommandPaint
|
||||
|
||||
|
||||
__all__ = []
|
||||
|
||||
Reference in New Issue
Block a user