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 ...@@ -6,6 +6,7 @@ Version 0.4.1 - UNRELEASED
* parallel and distributed processing is configurable in a dialog * parallel and distributed processing is configurable in a dialog
* visualize movements up to safety height properly * visualize movements up to safety height properly
* changed "simulation mode" for visualizing the machine moves * changed "simulation mode" for visualizing the machine moves
* added a very simple "pocketing" mode for 2D models
* added support for DXF feature "LWPOLYLINE" * added support for DXF feature "LWPOLYLINE"
* unify DropCutter behaviour for models that are higher than the defined bounding box * unify DropCutter behaviour for models that are higher than the defined bounding box
* always move up to safety height in this case * always move up to safety height in this case
......
...@@ -256,6 +256,23 @@ ...@@ -256,6 +256,23 @@
<column type="gfloat"/> <column type="gfloat"/>
</columns> </columns>
</object> </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"> <object class="GtkWindow" id="ProjectWindow">
<property name="title" translatable="yes">PyCAM</property> <property name="title" translatable="yes">PyCAM</property>
<property name="destroy_with_parent">True</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 ...@@ -2588,7 +2605,7 @@ Use the "Engrave Offset" to shift the position of the tool to the outside (posit
<child> <child>
<object class="GtkTable" id="table7"> <object class="GtkTable" id="table7">
<property name="visible">True</property> <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="n_columns">2</property>
<property name="column_spacing">2</property> <property name="column_spacing">2</property>
<property name="row_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.</ ...@@ -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> <property name="y_options">GTK_FILL</property>
</packing> </packing>
</child> </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> </object>
</child> </child>
</object> </object>
......
...@@ -491,6 +491,12 @@ class ContourModel(BaseModel): ...@@ -491,6 +491,12 @@ class ContourModel(BaseModel):
return None return None
return result 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): def check_for_collisions(self, callback=None, find_all_collisions=False):
""" check if lines in different line groups of this model collide """ check if lines in different line groups of this model collide
......
...@@ -129,6 +129,7 @@ PREFERENCES_DEFAULTS = { ...@@ -129,6 +129,7 @@ PREFERENCES_DEFAULTS = {
user's home directory on startup/shutdown""" user's home directory on startup/shutdown"""
GRID_TYPES = {"none": 0, "grid": 1, "automatic": 2} GRID_TYPES = {"none": 0, "grid": 1, "automatic": 2}
POCKETING_TYPES = ["none", "holes", "enclosed"]
# floating point color values are only available since gtk 2.16 # floating point color values are only available since gtk 2.16
GTK_COLOR_MAX = 65535.0 GTK_COLOR_MAX = 65535.0
...@@ -680,6 +681,11 @@ class ProjectGui: ...@@ -680,6 +681,11 @@ class ProjectGui:
self.handle_process_settings_change) self.handle_process_settings_change)
self.gui.get_object("ProcessSettingName").connect("changed", self.gui.get_object("ProcessSettingName").connect("changed",
self.handle_process_settings_change) 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 # get/set functions for the current tool/process/bounds/task
def get_current_item(table, item_list): def get_current_item(table, item_list):
index = self._treeview_get_active_index(table, item_list) index = self._treeview_get_active_index(table, item_list)
...@@ -1592,7 +1598,7 @@ class ProjectGui: ...@@ -1592,7 +1598,7 @@ class ProjectGui:
"MillingStyleConventional", "MillingStyleClimb", "MillingStyleConventional", "MillingStyleClimb",
"MillingStyleIgnore", "MaxStepDownControl", "MillingStyleIgnore", "MaxStepDownControl",
"MaterialAllowanceControl", "OverlapPercentControl", "MaterialAllowanceControl", "OverlapPercentControl",
"EngraveOffsetControl") "EngraveOffsetControl", "PocketingControl")
active_controls = { active_controls = {
"PushRemoveStrategy": ("GridDirectionX", "GridDirectionY", "PushRemoveStrategy": ("GridDirectionX", "GridDirectionY",
"GridDirectionXY", "MillingStyleConventional", "GridDirectionXY", "MillingStyleConventional",
...@@ -1610,7 +1616,8 @@ class ProjectGui: ...@@ -1610,7 +1616,8 @@ class ProjectGui:
"MillingStyleIgnore", "MaterialAllowanceControl", "MillingStyleIgnore", "MaterialAllowanceControl",
"OverlapPercentControl"), "OverlapPercentControl"),
"EngraveStrategy": ("MaxStepDownControl", "EngraveOffsetControl", "EngraveStrategy": ("MaxStepDownControl", "EngraveOffsetControl",
"MillingStyleConventional", "MillingStyleClimb"), "MillingStyleConventional", "MillingStyleClimb",
"PocketingControl"),
} }
for one_control in all_controls: for one_control in all_controls:
get_obj(one_control).set_sensitive(one_control in active_controls[strategy]) get_obj(one_control).set_sensitive(one_control in active_controls[strategy])
...@@ -2821,6 +2828,8 @@ class ProjectGui: ...@@ -2821,6 +2828,8 @@ class ProjectGui:
("MaxStepDownControl", "step_down"), ("MaxStepDownControl", "step_down"),
("EngraveOffsetControl", "engrave_offset")): ("EngraveOffsetControl", "engrave_offset")):
settings[key] = self.gui.get_object(objname).get_value() settings[key] = self.gui.get_object(objname).get_value()
settings["pocketing_type"] = POCKETING_TYPES[
self.gui.get_object("PocketingControl").get_active()]
return settings return settings
def _put_process_settings_to_gui(self, settings): def _put_process_settings_to_gui(self, settings):
...@@ -2848,6 +2857,9 @@ class ProjectGui: ...@@ -2848,6 +2857,9 @@ class ProjectGui:
("MaxStepDownControl", "step_down"), ("MaxStepDownControl", "step_down"),
("EngraveOffsetControl", "engrave_offset")): ("EngraveOffsetControl", "engrave_offset")):
self.gui.get_object(objname).set_value(settings[key]) 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 @gui_activity_guard
def handle_process_settings_change(self, widget=None, data=None): def handle_process_settings_change(self, widget=None, data=None):
...@@ -3363,10 +3375,11 @@ class ProjectGui: ...@@ -3363,10 +3375,11 @@ class ProjectGui:
toolpath_settings.set_process_settings( toolpath_settings.set_process_settings(
generator, postprocessor, process_settings["path_direction"], generator, postprocessor, process_settings["path_direction"],
process_settings["material_allowance"], process_settings["material_allowance"],
process_settings["overlap_percent"] / 100.0, process_settings["overlap_percent"],
process_settings["step_down"], process_settings["step_down"],
process_settings["engrave_offset"], process_settings["engrave_offset"],
process_settings["milling_style"]) process_settings["milling_style"],
process_settings["pocketing_type"])
return toolpath_settings return toolpath_settings
......
...@@ -137,6 +137,7 @@ milling_style: ignore ...@@ -137,6 +137,7 @@ milling_style: ignore
material_allowance: 0.0 material_allowance: 0.0
step_down: 3.0 step_down: 3.0
overlap_percent: 0 overlap_percent: 0
pocketing_type: none
[BoundsDefault] [BoundsDefault]
name: No Margin name: No Margin
...@@ -185,20 +186,20 @@ milling_style: ignore ...@@ -185,20 +186,20 @@ milling_style: ignore
engrave_offset: 0.0 engrave_offset: 0.0
step_down: 3.0 step_down: 3.0
material_allowance: 0.0 material_allowance: 0.0
overlap_percent: 0
pocketing_type: none
[Process0] [Process0]
name: Remove material name: Remove material
path_strategy: PushRemoveStrategy path_strategy: PushRemoveStrategy
material_allowance: 0.5 material_allowance: 0.5
step_down: 3.0 step_down: 3.0
overlap_percent: 0
[Process1] [Process1]
name: Carve contour name: Carve contour
path_strategy: ContourFollowStrategy path_strategy: ContourFollowStrategy
material_allowance: 0.2 material_allowance: 0.2
step_down: 1.5 step_down: 1.5
overlap_percent: 40
milling_style: conventional milling_style: conventional
[Process2] [Process2]
...@@ -211,8 +212,8 @@ overlap_percent: 60 ...@@ -211,8 +212,8 @@ overlap_percent: 60
name: Gravure name: Gravure
path_strategy: EngraveStrategy path_strategy: EngraveStrategy
step_down: 1.0 step_down: 1.0
overlap_percent: 50
milling_style: conventional milling_style: conventional
pocketing_type: none
[BoundsDefault] [BoundsDefault]
type: relative_margin type: relative_margin
...@@ -274,6 +275,7 @@ process: 3 ...@@ -274,6 +275,7 @@ process: 3
"overlap_percent": int, "overlap_percent": int,
"step_down": float, "step_down": float,
"engrave_offset": float, "engrave_offset": float,
"pocketing_type": str,
"tool": object, "tool": object,
"process": object, "process": object,
"bounds": object, "bounds": object,
...@@ -292,7 +294,8 @@ process: 3 ...@@ -292,7 +294,8 @@ process: 3
"speed"), "speed"),
"process": ("name", "path_strategy", "path_direction", "process": ("name", "path_strategy", "path_direction",
"milling_style", "material_allowance", "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", "bounds": ("name", "type", "x_low", "x_high", "y_low",
"y_high", "z_low", "z_high"), "y_high", "z_low", "z_high"),
"task": ("name", "tool", "process", "bounds", "enabled"), "task": ("name", "tool", "process", "bounds", "enabled"),
...@@ -589,10 +592,11 @@ class ToolpathSettings: ...@@ -589,10 +592,11 @@ class ToolpathSettings:
"postprocessor": str, "postprocessor": str,
"path_direction": str, "path_direction": str,
"material_allowance": float, "material_allowance": float,
"overlap": float, "overlap_percent": int,
"step_down": float, "step_down": float,
"engrave_offset": float, "engrave_offset": float,
"milling_style": str, "milling_style": str,
"pocketing_type": str,
}, },
} }
...@@ -700,8 +704,8 @@ class ToolpathSettings: ...@@ -700,8 +704,8 @@ class ToolpathSettings:
return "mm" return "mm"
def set_process_settings(self, generator, postprocessor, path_direction, def set_process_settings(self, generator, postprocessor, path_direction,
material_allowance=0.0, overlap=0.0, step_down=1.0, material_allowance=0.0, overlap_percent=0, step_down=1.0,
engrave_offset=0.0, milling_style="ignore"): engrave_offset=0.0, milling_style="ignore", pocketing_type="none"):
# TODO: this hack should be somewhere else, I guess # TODO: this hack should be somewhere else, I guess
if generator in ("ContourFollow", "EngraveCutter"): if generator in ("ContourFollow", "EngraveCutter"):
material_allowance = 0.0 material_allowance = 0.0
...@@ -710,10 +714,11 @@ class ToolpathSettings: ...@@ -710,10 +714,11 @@ class ToolpathSettings:
"postprocessor": postprocessor, "postprocessor": postprocessor,
"path_direction": path_direction, "path_direction": path_direction,
"material_allowance": material_allowance, "material_allowance": material_allowance,
"overlap": overlap, "overlap_percent": overlap_percent,
"step_down": step_down, "step_down": step_down,
"engrave_offset": engrave_offset, "engrave_offset": engrave_offset,
"milling_style": milling_style, "milling_style": milling_style,
"pocketing_type": pocketing_type,
} }
def get_process_settings(self): def get_process_settings(self):
......
...@@ -50,9 +50,9 @@ def generate_toolpath_from_settings(model, tp_settings, callback=None): ...@@ -50,9 +50,9 @@ def generate_toolpath_from_settings(model, tp_settings, callback=None):
return generate_toolpath(model, tp_settings.get_tool_settings(), return generate_toolpath(model, tp_settings.get_tool_settings(),
tp_settings.get_bounds(), process["path_direction"], tp_settings.get_bounds(), process["path_direction"],
process["generator"], process["postprocessor"], process["generator"], process["postprocessor"],
process["material_allowance"], process["overlap"], process["material_allowance"], process["overlap_percent"],
process["step_down"], process["engrave_offset"], process["step_down"], process["engrave_offset"],
process["milling_style"], process["milling_style"], process["pocketing_type"],
grid["type"], grid["distance_x"], grid["distance_y"], grid["type"], grid["distance_x"], grid["distance_y"],
grid["thickness"], grid["height"], grid["offset_x"], grid["thickness"], grid["height"], grid["offset_x"],
grid["offset_y"], grid["adjustments_x"], grid["adjustments_y"], grid["offset_y"], grid["adjustments_x"], grid["adjustments_y"],
...@@ -62,8 +62,8 @@ def generate_toolpath_from_settings(model, tp_settings, callback=None): ...@@ -62,8 +62,8 @@ def generate_toolpath_from_settings(model, tp_settings, callback=None):
def generate_toolpath(model, tool_settings=None, def generate_toolpath(model, tool_settings=None,
bounds=None, direction="x", bounds=None, direction="x",
path_generator="DropCutter", path_postprocessor="ZigZagCutter", path_generator="DropCutter", path_postprocessor="ZigZagCutter",
material_allowance=0, overlap=0, step_down=0, engrave_offset=0, material_allowance=0, overlap_percent=0, step_down=0, engrave_offset=0,
milling_style="ignore", milling_style="ignore", pocketing_type="none",
support_grid_type=None, support_grid_distance_x=None, support_grid_type=None, support_grid_distance_x=None,
support_grid_distance_y=None, support_grid_thickness=None, support_grid_distance_y=None, support_grid_thickness=None,
support_grid_height=None, support_grid_offset_x=None, support_grid_height=None, support_grid_offset_x=None,
...@@ -95,8 +95,8 @@ def generate_toolpath(model, tool_settings=None, ...@@ -95,8 +95,8 @@ def generate_toolpath(model, tool_settings=None,
@value path_postprocessor: any member of the PATH_POSTPROCESSORS set @value path_postprocessor: any member of the PATH_POSTPROCESSORS set
@type material_allowance: float @type material_allowance: float
@value material_allowance: the minimum distance between the tool and the model @value material_allowance: the minimum distance between the tool and the model
@type overlap: float @type overlap_percent: int
@value overlap: the overlap between two adjacent tool paths (0 <= overlap < 1) @value overlap_percent: the overlap between two adjacent tool paths (0..100) given in percent
@type step_down: float @type step_down: float
@value step_down: maximum height of each layer (for PushCutter) @value step_down: maximum height of each layer (for PushCutter)
@type engrave_offset: float @type engrave_offset: float
...@@ -125,7 +125,6 @@ def generate_toolpath(model, tool_settings=None, ...@@ -125,7 +125,6 @@ def generate_toolpath(model, tool_settings=None,
arguments arguments
""" """
log.debug("Starting toolpath generation") log.debug("Starting toolpath generation")
overlap = number(overlap)
step_down = number(step_down) step_down = number(step_down)
engrave_offset = number(engrave_offset) engrave_offset = number(engrave_offset)
if bounds is None: if bounds is None:
...@@ -145,6 +144,15 @@ def generate_toolpath(model, tool_settings=None, ...@@ -145,6 +144,15 @@ def generate_toolpath(model, tool_settings=None,
# contour model # contour model
trimesh_models = [] trimesh_models = []
contour_model = model 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 # create the grid model if requested
if (support_grid_type == "grid") \ if (support_grid_type == "grid") \
and (((not support_grid_distance_x is None) \ and (((not support_grid_distance_x is None) \
...@@ -201,6 +209,10 @@ def generate_toolpath(model, tool_settings=None, ...@@ -201,6 +209,10 @@ def generate_toolpath(model, tool_settings=None,
support_grid_minimum_bridges, support_grid_thickness, support_grid_minimum_bridges, support_grid_thickness,
support_grid_height, support_grid_length) support_grid_height, support_grid_length)
trimesh_models.append(support_grid_model) 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 # Adapt the contour_model to the engraving offset. This offset is
# considered to be part of the material_allowance. # considered to be part of the material_allowance.
if (not contour_model is None) and (engrave_offset != 0): if (not contour_model is None) and (engrave_offset != 0):
...@@ -237,29 +249,76 @@ def generate_toolpath(model, tool_settings=None, ...@@ -237,29 +249,76 @@ def generate_toolpath(model, tool_settings=None,
else: else:
# no collisions and no user interruption # no collisions and no user interruption
pass 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 # limit the contour model to the bounding box
if contour_model: if contour_model:
contour_model = contour_model.get_cropped_model(minx, maxx, miny, maxy, contour_model = contour_model.get_cropped_model(minx, maxx, miny, maxy,
minz, maxz) minz, maxz)
if contour_model is None: if contour_model is None:
return "No part of the contour model is within the bounding box." 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) physics = _get_physics(trimesh_models, cutter, calculation_backend)
if isinstance(physics, basestring): if isinstance(physics, basestring):
return physics return physics
generator = _get_pathgenerator_instance(trimesh_models, contour_model, generator = _get_pathgenerator_instance(trimesh_models, contour_model,
cutter, path_generator, path_postprocessor, physics, cutter, path_generator, path_postprocessor, physics,
milling_style=milling_style) milling_style)
if isinstance(generator, basestring): if isinstance(generator, basestring):
return generator return generator
overlap = overlap_percent / 100
if (overlap < 0) or (overlap >= 1): if (overlap < 0) or (overlap >= 1):
return "Invalid overlap value (%f): should be greater or equal 0 " \ return "Invalid overlap value (%f): should be greater or equal 0 " \
+ "and lower than 1" + "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