Commit 00a17c74 authored by sumpfralle's avatar sumpfralle

implemented a very simple pocketing mode for 2D models


git-svn-id: https://pycam.svn.sourceforge.net/svnroot/pycam/trunk@886 bbaffbd6-741e-11dd-a85d-61de82d9cad9
parent 66fe3ab6
......@@ -6,6 +6,7 @@ Version 0.4.1 - UNRELEASED
* parallel and distributed processing is configurable in a dialog
* visualize movements up to safety height properly
* changed "simulation mode" for visualizing the machine moves
* added a very simple "pocketing" mode for 2D models
* added support for DXF feature "LWPOLYLINE"
* unify DropCutter behaviour for models that are higher than the defined bounding box
* always move up to safety height in this case
......
......@@ -256,6 +256,23 @@
<column type="gfloat"/>
</columns>
</object>
<object class="GtkListStore" id="PocketingOptions">
<columns>
<!-- column-name name -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">none</col>
</row>
<row>
<col id="0" translatable="yes">holes</col>
</row>
<row>
<col id="0" translatable="yes">enclosed</col>
</row>
</data>
</object>
<object class="GtkWindow" id="ProjectWindow">
<property name="title" translatable="yes">PyCAM</property>
<property name="destroy_with_parent">True</property>
......@@ -2588,7 +2605,7 @@ Use the "Engrave Offset" to shift the position of the tool to the outside (posit
<child>
<object class="GtkTable" id="table7">
<property name="visible">True</property>
<property name="n_rows">4</property>
<property name="n_rows">5</property>
<property name="n_columns">2</property>
<property name="column_spacing">2</property>
<property name="row_spacing">2</property>
......@@ -2715,6 +2732,39 @@ Usually you will want to use the cutter radius here to cut around the outline.</
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="PocketingLabel">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Pocketing:</property>
</object>
<packing>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="PocketingControl">
<property name="visible">True</property>
<property name="model">PocketingOptions</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext7"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
</object>
</child>
</object>
......
......@@ -491,6 +491,12 @@ class ContourModel(BaseModel):
return None
return result
def get_copy(self):
result = ContourModel(plane=self._plane)
for group in self.get_polygons():
result.append(group)
return result
def check_for_collisions(self, callback=None, find_all_collisions=False):
""" check if lines in different line groups of this model collide
......
......@@ -129,6 +129,7 @@ PREFERENCES_DEFAULTS = {
user's home directory on startup/shutdown"""
GRID_TYPES = {"none": 0, "grid": 1, "automatic": 2}
POCKETING_TYPES = ["none", "holes", "enclosed"]
# floating point color values are only available since gtk 2.16
GTK_COLOR_MAX = 65535.0
......@@ -680,6 +681,11 @@ class ProjectGui:
self.handle_process_settings_change)
self.gui.get_object("ProcessSettingName").connect("changed",
self.handle_process_settings_change)
pocketing_selector = self.gui.get_object("PocketingControl")
self.settings.add_item("pocketing_type", pocketing_selector.get_active,
pocketing_selector.set_active)
pocketing_selector.connect("changed",
self.handle_process_settings_change)
# get/set functions for the current tool/process/bounds/task
def get_current_item(table, item_list):
index = self._treeview_get_active_index(table, item_list)
......@@ -1592,7 +1598,7 @@ class ProjectGui:
"MillingStyleConventional", "MillingStyleClimb",
"MillingStyleIgnore", "MaxStepDownControl",
"MaterialAllowanceControl", "OverlapPercentControl",
"EngraveOffsetControl")
"EngraveOffsetControl", "PocketingControl")
active_controls = {
"PushRemoveStrategy": ("GridDirectionX", "GridDirectionY",
"GridDirectionXY", "MillingStyleConventional",
......@@ -1610,7 +1616,8 @@ class ProjectGui:
"MillingStyleIgnore", "MaterialAllowanceControl",
"OverlapPercentControl"),
"EngraveStrategy": ("MaxStepDownControl", "EngraveOffsetControl",
"MillingStyleConventional", "MillingStyleClimb"),
"MillingStyleConventional", "MillingStyleClimb",
"PocketingControl"),
}
for one_control in all_controls:
get_obj(one_control).set_sensitive(one_control in active_controls[strategy])
......@@ -2821,6 +2828,8 @@ class ProjectGui:
("MaxStepDownControl", "step_down"),
("EngraveOffsetControl", "engrave_offset")):
settings[key] = self.gui.get_object(objname).get_value()
settings["pocketing_type"] = POCKETING_TYPES[
self.gui.get_object("PocketingControl").get_active()]
return settings
def _put_process_settings_to_gui(self, settings):
......@@ -2848,6 +2857,9 @@ class ProjectGui:
("MaxStepDownControl", "step_down"),
("EngraveOffsetControl", "engrave_offset")):
self.gui.get_object(objname).set_value(settings[key])
if settings["pocketing_type"] in POCKETING_TYPES:
self.gui.get_object("PocketingControl").set_active(
POCKETING_TYPES.index(settings["pocketing_type"]))
@gui_activity_guard
def handle_process_settings_change(self, widget=None, data=None):
......@@ -3363,10 +3375,11 @@ class ProjectGui:
toolpath_settings.set_process_settings(
generator, postprocessor, process_settings["path_direction"],
process_settings["material_allowance"],
process_settings["overlap_percent"] / 100.0,
process_settings["overlap_percent"],
process_settings["step_down"],
process_settings["engrave_offset"],
process_settings["milling_style"])
process_settings["milling_style"],
process_settings["pocketing_type"])
return toolpath_settings
......
......@@ -137,6 +137,7 @@ milling_style: ignore
material_allowance: 0.0
step_down: 3.0
overlap_percent: 0
pocketing_type: none
[BoundsDefault]
name: No Margin
......@@ -185,20 +186,20 @@ milling_style: ignore
engrave_offset: 0.0
step_down: 3.0
material_allowance: 0.0
overlap_percent: 0
pocketing_type: none
[Process0]
name: Remove material
path_strategy: PushRemoveStrategy
material_allowance: 0.5
step_down: 3.0
overlap_percent: 0
[Process1]
name: Carve contour
path_strategy: ContourFollowStrategy
material_allowance: 0.2
step_down: 1.5
overlap_percent: 40
milling_style: conventional
[Process2]
......@@ -211,8 +212,8 @@ overlap_percent: 60
name: Gravure
path_strategy: EngraveStrategy
step_down: 1.0
overlap_percent: 50
milling_style: conventional
pocketing_type: none
[BoundsDefault]
type: relative_margin
......@@ -274,6 +275,7 @@ process: 3
"overlap_percent": int,
"step_down": float,
"engrave_offset": float,
"pocketing_type": str,
"tool": object,
"process": object,
"bounds": object,
......@@ -292,7 +294,8 @@ process: 3
"speed"),
"process": ("name", "path_strategy", "path_direction",
"milling_style", "material_allowance",
"overlap_percent", "step_down", "engrave_offset"),
"overlap_percent", "step_down", "engrave_offset",
"pocketing_type"),
"bounds": ("name", "type", "x_low", "x_high", "y_low",
"y_high", "z_low", "z_high"),
"task": ("name", "tool", "process", "bounds", "enabled"),
......@@ -589,10 +592,11 @@ class ToolpathSettings:
"postprocessor": str,
"path_direction": str,
"material_allowance": float,
"overlap": float,
"overlap_percent": int,
"step_down": float,
"engrave_offset": float,
"milling_style": str,
"pocketing_type": str,
},
}
......@@ -700,8 +704,8 @@ class ToolpathSettings:
return "mm"
def set_process_settings(self, generator, postprocessor, path_direction,
material_allowance=0.0, overlap=0.0, step_down=1.0,
engrave_offset=0.0, milling_style="ignore"):
material_allowance=0.0, overlap_percent=0, step_down=1.0,
engrave_offset=0.0, milling_style="ignore", pocketing_type="none"):
# TODO: this hack should be somewhere else, I guess
if generator in ("ContourFollow", "EngraveCutter"):
material_allowance = 0.0
......@@ -710,10 +714,11 @@ class ToolpathSettings:
"postprocessor": postprocessor,
"path_direction": path_direction,
"material_allowance": material_allowance,
"overlap": overlap,
"overlap_percent": overlap_percent,
"step_down": step_down,
"engrave_offset": engrave_offset,
"milling_style": milling_style,
"pocketing_type": pocketing_type,
}
def get_process_settings(self):
......
......@@ -50,9 +50,9 @@ def generate_toolpath_from_settings(model, tp_settings, callback=None):
return generate_toolpath(model, tp_settings.get_tool_settings(),
tp_settings.get_bounds(), process["path_direction"],
process["generator"], process["postprocessor"],
process["material_allowance"], process["overlap"],
process["material_allowance"], process["overlap_percent"],
process["step_down"], process["engrave_offset"],
process["milling_style"],
process["milling_style"], process["pocketing_type"],
grid["type"], grid["distance_x"], grid["distance_y"],
grid["thickness"], grid["height"], grid["offset_x"],
grid["offset_y"], grid["adjustments_x"], grid["adjustments_y"],
......@@ -62,8 +62,8 @@ def generate_toolpath_from_settings(model, tp_settings, callback=None):
def generate_toolpath(model, tool_settings=None,
bounds=None, direction="x",
path_generator="DropCutter", path_postprocessor="ZigZagCutter",
material_allowance=0, overlap=0, step_down=0, engrave_offset=0,
milling_style="ignore",
material_allowance=0, overlap_percent=0, step_down=0, engrave_offset=0,
milling_style="ignore", pocketing_type="none",
support_grid_type=None, support_grid_distance_x=None,
support_grid_distance_y=None, support_grid_thickness=None,
support_grid_height=None, support_grid_offset_x=None,
......@@ -95,8 +95,8 @@ def generate_toolpath(model, tool_settings=None,
@value path_postprocessor: any member of the PATH_POSTPROCESSORS set
@type material_allowance: float
@value material_allowance: the minimum distance between the tool and the model
@type overlap: float
@value overlap: the overlap between two adjacent tool paths (0 <= overlap < 1)
@type overlap_percent: int
@value overlap_percent: the overlap between two adjacent tool paths (0..100) given in percent
@type step_down: float
@value step_down: maximum height of each layer (for PushCutter)
@type engrave_offset: float
......@@ -125,7 +125,6 @@ def generate_toolpath(model, tool_settings=None,
arguments
"""
log.debug("Starting toolpath generation")
overlap = number(overlap)
step_down = number(step_down)
engrave_offset = number(engrave_offset)
if bounds is None:
......@@ -145,6 +144,15 @@ def generate_toolpath(model, tool_settings=None,
# contour model
trimesh_models = []
contour_model = model
# Due to some weirdness the height of the drill must be bigger than the
# object's size. Otherwise some collisions are not detected.
cutter_height = 4 * abs(maxz - minz)
cutter = pycam.Cutters.get_tool_from_settings(tool_settings, cutter_height)
if isinstance(cutter, basestring):
return cutter
if not path_generator in ("EngraveCutter", "ContourFollow"):
# material allowance is not available for these two strategies
cutter.set_required_distance(material_allowance)
# create the grid model if requested
if (support_grid_type == "grid") \
and (((not support_grid_distance_x is None) \
......@@ -201,6 +209,10 @@ def generate_toolpath(model, tool_settings=None,
support_grid_minimum_bridges, support_grid_thickness,
support_grid_height, support_grid_length)
trimesh_models.append(support_grid_model)
elif (not support_grid_type) or (support_grid_type == "none"):
pass
else:
return "Invalid support grid type selected: %s" % support_grid_type
# Adapt the contour_model to the engraving offset. This offset is
# considered to be part of the material_allowance.
if (not contour_model is None) and (engrave_offset != 0):
......@@ -237,29 +249,76 @@ def generate_toolpath(model, tool_settings=None,
else:
# no collisions and no user interruption
pass
# check the pocketing type
if (not contour_model is None) and (pocketing_type != "none"):
if not callback is None:
callback(text="Generating pocketing polygons ...")
new_polygons = []
pocketing_offset = cutter.radius * 1.8
# TODO: this is an arbitrary limit to avoid infinite loops
pocketing_limit = 1000
base_polygons = []
other_polygons = []
if pocketing_type == "holes":
# fill polygons with negative area
for poly in contour_model.get_polygons():
if poly.is_closed and not poly.is_outer():
base_polygons.append(poly)
else:
other_polygons.append(poly)
elif pocketing_type == "enclosed":
# fill polygons with positive area
pocketing_offset *= -1
for poly in contour_model.get_polygons():
if poly.is_closed and poly.is_outer():
base_polygons.append(poly)
else:
other_polygons.append(poly)
else:
return "Unknown pocketing type given (not one of 'none', 'holes', " \
+ "'enclosed'): %s" % str(pocketing_type)
# For now we use only the polygons that do not surround eny other
# polygons. Sorry - the pocketing is currently very simple ...
base_filtered_polygons = []
for candidate in base_polygons:
for other in other_polygons:
if candidate.is_polygon_inside(other):
break
else:
base_filtered_polygons.append(candidate)
# start the pocketing for all remaining polygons
pocket_polygons = []
for base_polygon in base_filtered_polygons:
current_queue = [base_polygon]
next_queue = []
pocket_depth = 0
while current_queue and (pocket_depth < pocketing_limit):
for poly in current_queue:
result = poly.get_offset_polygons(pocketing_offset)
pocket_polygons.extend(result)
next_queue.extend(result)
pocket_depth += 1
current_queue = next_queue
next_queue = []
# use a copy instead of the original
contour_model = contour_model.get_copy()
for pocket in pocket_polygons:
contour_model.append(pocket)
# limit the contour model to the bounding box
if contour_model:
contour_model = contour_model.get_cropped_model(minx, maxx, miny, maxy,
minz, maxz)
if contour_model is None:
return "No part of the contour model is within the bounding box."
# Due to some weirdness the height of the drill must be bigger than the
# object's size. Otherwise some collisions are not detected.
cutter_height = 4 * abs(maxz - minz)
cutter = pycam.Cutters.get_tool_from_settings(tool_settings, cutter_height)
if isinstance(cutter, basestring):
return cutter
if not path_generator in ("EngraveCutter", "ContourFollow"):
# material allowance is not available for these two strategies
cutter.set_required_distance(material_allowance)
physics = _get_physics(trimesh_models, cutter, calculation_backend)
if isinstance(physics, basestring):
return physics
generator = _get_pathgenerator_instance(trimesh_models, contour_model,
cutter, path_generator, path_postprocessor, physics,
milling_style=milling_style)
milling_style)
if isinstance(generator, basestring):
return generator
overlap = overlap_percent / 100
if (overlap < 0) or (overlap >= 1):
return "Invalid overlap value (%f): should be greater or equal 0 " \
+ "and lower than 1"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment