Пример #1
0
 def update_model_dimensions(self, widget=None):
     dimension_bar = self.gui.get_object("DimensionTable")
     models = [m.get_model() for m in self.core.get("models").get_visible()]
     model_box = pycam.Geometry.Model.get_combined_bounds(models)
     if model_box is None:
         model_box = Box3D(Point3D(0, 0, 0), Point3D(0, 0, 0))
     bounds = self.core.get("bounds").get_selected()
     if self.core.get("show_dimensions"):
         for value, label_suffix in ((model_box.lower.x,
                                      "XMin"), (model_box.upper.x, "XMax"),
                                     (model_box.lower.y,
                                      "YMin"), (model_box.upper.y, "YMax"),
                                     (model_box.lower.z,
                                      "ZMin"), (model_box.upper.z, "ZMax")):
             label_name = "ModelCorner%s" % label_suffix
             value = "%.3f" % value
             if label_suffix.lower().endswith("max"):
                 value += self.core.get("unit_string")
             self.gui.get_object(label_name).set_label(value)
         if bounds:
             bounds_box = bounds.get_absolute_limits()
             if bounds_box is None:
                 bounds_size = ("", "", "")
             else:
                 bounds_size = [
                     "%.3f %s" % (high - low, self.core.get("unit_string"))
                     for low, high in zip(bounds_box.lower,
                                          bounds_box.upper)
                 ]
             for axis, size_string in zip("xyz", bounds_size):
                 self.gui.get_object("model_dim_" +
                                     axis).set_text(size_string)
         dimension_bar.show()
     else:
         dimension_bar.hide()
Пример #2
0
def get_combined_bounds(models):
    low = [None, None, None]
    high = [None, None, None]
    for model in models:
        if (low[0] is None) or ((model.minx is not None) and
                                (model.minx < low[0])):
            low[0] = model.minx
        if (low[1] is None) or ((model.miny is not None) and
                                (model.miny < low[1])):
            low[1] = model.miny
        if (low[2] is None) or ((model.minz is not None) and
                                (model.minz < low[2])):
            low[2] = model.minz
        if (high[0] is None) or ((model.maxx is not None) and
                                 (model.maxx > high[0])):
            high[0] = model.maxx
        if (high[1] is None) or ((model.maxy is not None) and
                                 (model.maxy > high[1])):
            high[1] = model.maxy
        if (high[2] is None) or ((model.maxz is not None) and
                                 (model.maxz > high[2])):
            high[2] = model.maxz
    if None in low or None in high:
        return None
    else:
        return Box3D(Point3D(*low), Point3D(*high))
Пример #3
0
def get_lines_grid(models, box, layer_distance, line_distance=None, step_width=None,
                   milling_style=MillingStyle.CONVENTIONAL, start_position=StartPosition.Z,
                   pocketing_type=PocketingType.NONE, skip_first_layer=False, callback=None):
    _log.debug("Calculating lines grid: {} model(s), z={}..{} ({}), line_distance={}, "
               "step_width={}".format(len(models), box.lower, box.upper, layer_distance,
                                      line_distance, step_width))
    # the lower limit is never below the model
    polygons = _get_sorted_polygons(models, callback=callback)
    if polygons:
        low_limit_lines = min([polygon.minz for polygon in polygons])
        new_lower = Point3D(box.lower.x, box.lower.y, max(box.lower.z, low_limit_lines))
        box = Box3D(new_lower, box.upper)
    # calculate pockets
    if pocketing_type != PocketingType.NONE:
        if callback is not None:
            callback(text="Generating pocketing polygons ...")
        polygons = get_pocketing_polygons(polygons, line_distance, pocketing_type,
                                          callback=callback)
    # extract lines in correct order from all polygons
    lines = []
    for polygon in polygons:
        if callback:
            callback()
        if polygon.is_closed and (milling_style == MillingStyle.CONVENTIONAL):
            polygon = polygon.copy()
            polygon.reverse_direction()
        for line in polygon.get_lines():
            lines.append(line)
    if isiterable(layer_distance):
        layers = layer_distance
    elif layer_distance is None:
        # only one layer
        layers = [box.lower.z]
    else:
        layers = floatrange(box.lower.z, box.upper.z, inc=layer_distance,
                            reverse=bool(start_position & StartPosition.Z))
    # turn the generator into a list - otherwise the slicing fails
    layers = list(layers)
    # engrave ignores the top layer
    if skip_first_layer and (len(layers) > 1):
        layers = layers[1:]
    last_z = None
    _log.debug("Pocketing Polygon Layers: %d", len(layers))
    if layers:
        # the upper layers are used for PushCutter operations
        for z in layers[:-1]:
            _log.debug2("Pocketing Polygon Layers: calculating z=%g for PushCutter", z)
            if callback:
                callback()
            yield get_lines_layer(lines, z, last_z=last_z, step_width=None,
                                  milling_style=milling_style)
            last_z = z
        # the last layer is used for a DropCutter operation
        if callback:
            callback()
        _log.debug2("Pocketing Polygon Layers: calculating z=%g (lowest layer) for DropCutter",
                    layers[-1])
        yield get_lines_layer(lines, layers[-1], last_z=last_z, step_width=step_width,
                              milling_style=milling_style)
Пример #4
0
 def xtest_fixed_grid(self):
     box = Box3D(Point3D(-1, -1, -1), Point3D(1, 1, 1))
     fixed_grid = get_fixed_grid(box,
                                 0.5,
                                 line_distance=0.8,
                                 step_width=0.6,
                                 grid_direction=GridDirection.X,
                                 milling_style=MillingStyle.IGNORE,
                                 start_position=StartPosition.Z)
     resolved_fixed_grid = [list(layer) for layer in fixed_grid]
     print(resolved_fixed_grid)
Пример #5
0
 def _get_bounds(self, models=None):
     if not models:
         models = self.core.get("models").get_selected()
     models = [m.model for m in models]
     box = pycam.Geometry.Model.get_combined_bounds(models)
     if box is None:
         return None
     else:
         # TODO: the x/y offset should be configurable via a control
         margin = 5
         return Box3D(Point3D(box.lower.x - margin, box.lower.y - margin, box.lower.z),
                      Point3D(box.upper.x + margin, box.upper.y + margin, box.upper.z))
Пример #6
0
 def get_absolute_limits(self, tool_radius=None, models=None):
     get_low_value = lambda axis: self["parameters"]["BoundaryLow%s" % "XYZ"
                                                     [axis]]
     get_high_value = lambda axis: self["parameters"]["BoundaryHigh%s" %
                                                      "XYZ"[axis]]
     if self["parameters"]["TypeRelativeMargin"]:
         # choose the appropriate set of models
         if self["parameters"]["Models"]:
             # configured models always take precedence
             models = self["parameters"]["Models"]
         elif models:
             # use the supplied models (e.g. for toolpath calculation)
             pass
         else:
             # use all visible models -> for live visualization
             models = self.core.get("models").get_visible()
         model_box = pycam.Geometry.Model.get_combined_bounds(
             [model.model for model in models])
         if model_box is None:
             # zero-sized models -> no action
             return None
         is_percent = _RELATIVE_UNIT[self["parameters"]
                                     ["RelativeUnit"]] == "%"
         low, high = [], []
         if is_percent:
             for axis in range(3):
                 dim = model_box.upper[axis] - model_box.lower[axis]
                 low.append(model_box.lower[axis] -
                            (get_low_value(axis) / 100.0 * dim))
                 high.append(model_box.upper[axis] +
                             (get_high_value(axis) / 100.0 * dim))
         else:
             for axis in range(3):
                 low.append(model_box.lower[axis] - get_low_value(axis))
                 high.append(model_box.upper[axis] + get_high_value(axis))
     else:
         low, high = [], []
         for axis in range(3):
             low.append(get_low_value(axis))
             high.append(get_high_value(axis))
     tool_limit = _BOUNDARY_MODES[self["parameters"]["ToolLimit"]]
     # apply inside/along/outside if a tool is given
     if tool_radius and (tool_limit != "along"):
         if tool_limit == "inside":
             offset = -tool_radius
         else:
             offset = tool_radius
         # apply offset only for x and y
         for index in range(2):
             low[index] -= offset
             high[index] += offset
     return Box3D(Point3D(*low), Point3D(*high))
Пример #7
0
    def get_absolute_limits(self, reference=None):
        """ calculate the current absolute limits of the Bounds instance

        @value reference: a reference object described by a tuple (or list) of
            three item. These three values describe only the lower boundary of
            this object (for the x, y and z axes). Each item must be a float
            value. This argument is ignored for the boundary type "TYPE_CUSTOM".
        @type reference: (tuple|list) of float
        @returns: a tuple of two lists containg the low and high limits
        @rvalue: tuple(list)
        """
        # use the default reference if none was given
        if reference is None:
            reference = self.reference
        # check if a reference is given (if necessary)
        if self.bounds_type \
                in (Bounds.TYPE_RELATIVE_MARGIN, Bounds.TYPE_FIXED_MARGIN):
            if reference is None:
                raise ValueError(
                    "any non-custom boundary definition requires a reference "
                    "object for calculating absolute limits")
            else:
                ref_low, ref_high = reference.get_absolute_limits()
        low = [None] * 3
        high = [None] * 3
        # calculate the absolute limits
        if self.bounds_type == Bounds.TYPE_RELATIVE_MARGIN:
            for index in range(3):
                dim_width = ref_high[index] - ref_low[index]
                low[index] = ref_low[index] - self.bounds_low[index] * dim_width
                high[index] = ref_high[
                    index] + self.bounds_high[index] * dim_width
        elif self.bounds_type == Bounds.TYPE_FIXED_MARGIN:
            for index in range(3):
                low[index] = ref_low[index] - self.bounds_low[index]
                high[index] = ref_high[index] + self.bounds_high[index]
        elif self.bounds_type == Bounds.TYPE_CUSTOM:
            for index in range(3):
                low[index] = number(self.bounds_low[index])
                high[index] = number(self.bounds_high[index])
        else:
            # this should not happen
            raise NotImplementedError(
                "the function 'get_absolute_limits' is currently not "
                "implemented for the bounds_type '%s'" % str(self.bounds_type))
        return Box3D(Point3D(low), Point3D(high))
Пример #8
0
def run_dropcutter():
    """ Run DropCutter on standard PyCAM sample plaque """
    progress_bar = ConsoleProgressBar(sys.stdout)

    overlap = .6
    layer_distance = 1
    tool = CylindricalCutter(10)
    path_generator = DropCutter(PathAccumulator())
    bounds = Bounds(Bounds.TYPE_CUSTOM, Box3D(Point3D(model.minx-5, model.miny-5, model.minz),
                                              Point3D(model.maxx+5, model.maxy+5, model.maxz)))

    low, high = bounds.get_absolute_limits()
    line_distance = 2 * tool.radius * (1.0 - overlap)

    motion_grid = get_fixed_grid((low, high), layer_distance,
                                 line_distance, tool.radius / 4.0)
    path_generator.GenerateToolPath(tool, [model], motion_grid, minz=low[2], maxz=high[2],
                                    draw_callback=progress_bar.update)
Пример #9
0
 def test_fixed_grid(self):
     box = Box3D(Point3D(-3, -2, -1), Point3D(3, 2, 1))
     grid = _resolve_nested(
         3,
         get_fixed_grid(box,
                        1.2,
                        line_distance=2.0,
                        step_width=None,
                        grid_direction=GridDirection.X,
                        milling_style=MillingStyle.CONVENTIONAL,
                        start_position=StartPosition.Z))
     self.assert_almost_equal_grid(grid, (
         (((-3, -2, 1), (3, -2, 1)), ((-3, 0, 1), (3, 0, 1)), ((-3, 2, 1),
                                                               (3, 2, 1))),
         (((3, 2, 0), (-3, 2, 0)), ((3, 0, 0), (-3, 0, 0)), ((3, -2, 0),
                                                             (-3, -2, 0))),
         (((-3, -2, -1), (3, -2, -1)), ((-3, 0, -1), (3, 0, -1)),
          ((-3, 2, -1), (3, 2, -1))),
     ))
Пример #10
0
 def _get_bounds_instance_from_dict(self, indict):
     """ get Bounds instances for each bounds definition
     @value model: the model that should be used for relative margins
     @type model: pycam.Geometry.Model.Model or callable
     @returns: list of Bounds instances
     @rtype: list(Bounds)
     """
     low_bounds = (indict["x_low"], indict["y_low"], indict["z_low"])
     high_bounds = (indict["x_high"], indict["y_high"], indict["z_high"])
     if indict["type"] == "relative_margin":
         bounds_type = Bounds.TYPE_RELATIVE_MARGIN
     elif indict["type"] == "fixed_margin":
         bounds_type = Bounds.TYPE_FIXED_MARGIN
     else:
         bounds_type = Bounds.TYPE_CUSTOM
     new_bound = Bounds(bounds_type,
                        Box3D(Point3D(*low_bounds), Point3D(*high_bounds)))
     new_bound.set_name(indict["name"])
     return new_bound
Пример #11
0
    def __init__(self, bounds_type=None, box=None, reference=None):
        """ create a new Bounds instance

        @value bounds_type: any of TYPE_RELATIVE_MARGIN | TYPE_FIXED_MARGIN |
            TYPE_CUSTOM
        @type bounds_type: int
        @value bounds_low: the lower margin of the boundary compared to the
            reference object (for TYPE_RELATIVE_MARGIN | TYPE_FIXED_MARGIN) or
            the specific boundary values (for TYPE_CUSTOM). Only the lower
            values of the three axes (x, y and z) are given.
        @type bounds_low: (tuple|list) of float
        @value bounds_high: see 'bounds_low'
        @type bounds_high: (tuple|list) of float
        @value reference: optional default reference Bounds instance
        @type reference: Bounds
        """
        self.name = "No name"
        self.set_type(bounds_type)
        if box is None:
            box = Box3D(Point3D(0, 0, 0), Point3D(0, 0, 0))
        self.set_bounds(box)
        self.reference = reference
Пример #12
0
 def get_bounds(self):
     return Bounds(
         Bounds.TYPE_CUSTOM,
         Box3D(Point3D(self.minx, self.miny, self.minz),
               Point3D(self.maxx, self.maxy, self.maxz)))
Пример #13
0
 def get_bounds(self):
     return Box3D(Point3D(*self.bounds_low), Point3D(*self.bounds_high))
Пример #14
0
 def get_bounds(self):
     low = (self.bounds["minx"], self.bounds["miny"], self.bounds["minz"])
     high = (self.bounds["maxx"], self.bounds["maxy"], self.bounds["maxz"])
     return Bounds(Bounds.TYPE_CUSTOM, Box3D(Point3D(low), Point3D(high)))
Пример #15
0
 def change_unit_apply(self, widget=None, data=None, apply_scale=True):
     # TODO: move tool/process/task related code to these plugins
     new_unit = self.gui.get_object("unit_control").get_active_text()
     factors = {("mm", "inch"): 1 / 25.4, ("inch", "mm"): 25.4}
     conversion = (self._last_unit, new_unit)
     if conversion in factors.keys():
         factor = factors[conversion]
         if apply_scale:
             if self.gui.get_object("UnitChangeModel").get_active():
                 # transform the model if it is selected
                 # keep the original center of the model
                 self.core.emit_event("model-change-before")
                 models = self.core.get("models")
                 progress = self.core.get("progress")
                 progress.disable_cancel()
                 progress.set_multiple(len(models), "Scaling model")
                 for model in models:
                     new_x, new_y, new_z = ((model.maxx + model.minx) / 2,
                                            (model.maxy + model.miny) / 2,
                                            (model.maxz + model.minz) / 2)
                     model.scale(factor, callback=progress.update)
                     cur_x, cur_y, cur_z = self._get_model_center()
                     model.shift(new_x - cur_x,
                                 new_y - cur_y,
                                 new_z - cur_z,
                                 callback=progress.update)
                     progress.update_multiple()
                 progress.finish()
             if self.gui.get_object("UnitChangeProcesses").get_active():
                 # scale the process settings
                 for process in self.core.get("processes"):
                     for key in ("MaterialAllowanceControl",
                                 "MaxStepDownControl",
                                 "EngraveOffsetControl"):
                         process[key] *= factor
             if self.gui.get_object("UnitChangeBounds").get_active():
                 # scale the boundaries and keep their center
                 for bounds in self.core.get("bounds"):
                     box = bounds.get_bounds()
                     if bounds.get_type() == Bounds.TYPE_FIXED_MARGIN:
                         low = Point3D(pmul(box.lower, factor))
                         high = Point3D(pmul(box.upper, factor))
                         bounds.set_bounds(Box3D(low, high))
                     elif bounds.get_type() == Bounds.TYPE_CUSTOM:
                         center = box.get_center()
                         low = Point3D(*[
                             c - factor * (c - l)
                             for l, c in zip(box.lower, center)
                         ])
                         high = Point3D(*[
                             c + factor * (h - c)
                             for h, c in zip(box.upper, center)
                         ])
                         bounds.set_bounds(Box3D(low, high))
                     elif bounds.get_type() == Bounds.TYPE_RELATIVE_MARGIN:
                         # no need to change relative margins
                         pass
             if self.gui.get_object("UnitChangeTools").get_active():
                 # scale all tool dimensions
                 for tool in self.core.get("tools"):
                     for key in ("tool_radius", "torus_radius"):
                         # TODO: fix this invalid access
                         tool[key] *= factor
     self.unit_change_window.hide()
     # store the current unit (for the next run of this function)
     self._last_unit = new_unit
     # update all labels containing the unit size
     self.update_unit_labels()
     # redraw the model
     self.core.emit_event("model-change-after")
Пример #16
0
 def get_referenced_bounds(self, reference):
     return Bounds(self.bounds_type,
                   Box3D(Point3D(*self.bounds_low),
                         Point3D(*self.bounds_high)),
                   reference=reference)