Exemplo n.º 1
0
def draw_axes(settings):
    GL.glMatrixMode(GL.GL_MODELVIEW)
    GL.glLoadIdentity()
    # GL.glTranslatef(0, 0, -2)
    size_x = abs(settings.get("maxx"))
    size_y = abs(settings.get("maxy"))
    size_z = abs(settings.get("maxz"))
    size = number(1.7) * max(size_x, size_y, size_z)
    # the divider is just based on playing with numbers
    scale = size / number(1500.0)
    string_distance = number(1.1) * size
    GL.glBegin(GL.GL_LINES)
    GL.glColor3f(1, 0, 0)
    GL.glVertex3f(0, 0, 0)
    GL.glVertex3f(size, 0, 0)
    GL.glEnd()
    draw_string(string_distance, 0, 0, "xy", "X", scale=scale)
    GL.glBegin(GL.GL_LINES)
    GL.glColor3f(0, 1, 0)
    GL.glVertex3f(0, 0, 0)
    GL.glVertex3f(0, size, 0)
    GL.glEnd()
    draw_string(0, string_distance, 0, "yz", "Y", scale=scale)
    GL.glBegin(GL.GL_LINES)
    GL.glColor3f(0, 0, 1)
    GL.glVertex3f(0, 0, 0)
    GL.glVertex3f(0, 0, size)
    GL.glEnd()
    draw_string(0, 0, string_distance, "xz", "Z", scale=scale)
Exemplo n.º 2
0
def get_rotation_matrix_axis_angle(rot_axis, rot_angle, use_radians=True):
    """ calculate rotation matrix for a normalized vector and an angle

    see http://mathworld.wolfram.com/RotationMatrix.html
    @type rot_axis: tuple(float)
    @value rot_axis: the vector describes the rotation axis. Its length should
        be 1.0 (normalized).
    @type rot_angle: float
    @value rot_angle: rotation angle (radiant)
    @rtype: tuple(tuple(float))
    @return: the roation matrix (3x3)
    """
    if not use_radians:
        rot_angle *= math.pi / 180
    sin = number(math.sin(rot_angle))
    cos = number(math.cos(rot_angle))
    return ((cos + rot_axis[0] * rot_axis[0] * (1 - cos),
             rot_axis[0] * rot_axis[1] * (1 - cos) - rot_axis[2] * sin,
             rot_axis[0] * rot_axis[2] * (1 - cos) + rot_axis[1] * sin),
            (rot_axis[1] * rot_axis[0] * (1 - cos) + rot_axis[2] * sin,
             cos + rot_axis[1] * rot_axis[1] * (1 - cos),
             rot_axis[1] * rot_axis[2] * (1 - cos) - rot_axis[0] * sin),
            (rot_axis[2] * rot_axis[0] * (1 - cos) - rot_axis[1] * sin,
             rot_axis[2] * rot_axis[1] * (1 - cos) + rot_axis[0] * sin,
             cos + rot_axis[2] * rot_axis[2] * (1 - cos)))
Exemplo n.º 3
0
def draw_axes(settings):
    GL.glMatrixMode(GL.GL_MODELVIEW)
    GL.glLoadIdentity()
    #GL.glTranslatef(0, 0, -2)
    size_x = abs(settings.get("maxx"))
    size_y = abs(settings.get("maxy"))
    size_z = abs(settings.get("maxz"))
    size = number(1.7) * max(size_x, size_y, size_z)
    # the divider is just based on playing with numbers
    scale = size / number(1500.0)
    string_distance = number(1.1) * size
    GL.glBegin(GL.GL_LINES)
    GL.glColor3f(1, 0, 0)
    GL.glVertex3f(0, 0, 0)
    GL.glVertex3f(size, 0, 0)
    GL.glEnd()
    draw_string(string_distance, 0, 0, 'xy', "X", scale=scale)
    GL.glBegin(GL.GL_LINES)
    GL.glColor3f(0, 1, 0)
    GL.glVertex3f(0, 0, 0)
    GL.glVertex3f(0, size, 0)
    GL.glEnd()
    draw_string(0, string_distance, 0, 'yz', "Y", scale=scale)
    GL.glBegin(GL.GL_LINES)
    GL.glColor3f(0, 0, 1)
    GL.glVertex3f(0, 0, 0)
    GL.glVertex3f(0, 0, size)
    GL.glEnd()
    draw_string(0, 0, string_distance, 'xz', "Z", scale=scale)
Exemplo n.º 4
0
 def move_camera_by_screen(self, x_move, y_move, max_model_shift):
     """ move the camera acoording to a mouse movement
     @type x_move: int
     @value x_move: movement of the mouse along the x axis
     @type y_move: int
     @value y_move: movement of the mouse along the y axis
     @type max_model_shift: float
     @value max_model_shift: maximum shifting of the model view (e.g. for
         x_move == screen width)
     """
     factors_x, factors_y = self._get_axes_vectors()
     width, height = self._get_screen_dimensions()
     # relation of x/y movement to the respective screen dimension
     win_x_rel = (-2 * x_move) / float(width) / math.sin(self.view["fovy"])
     win_y_rel = (-2 * y_move) / float(height) / math.sin(self.view["fovy"])
     # This code is completely arbitrarily based on trial-and-error for
     # finding a nice movement speed for all distances.
     # Anyone with a better approach should just fix this.
     distance_vector = self.get("distance")
     distance = float(sqrt(sum([dim**2 for dim in distance_vector])))
     win_x_rel *= math.cos(win_x_rel / distance)**20
     win_y_rel *= math.cos(win_y_rel / distance)**20
     # update the model position that should be centered on the screen
     old_center = self.view["center"]
     new_center = []
     for i in range(3):
         new_center.append(old_center[i] \
                 + max_model_shift * (number(win_x_rel) * factors_x[i] \
                 + number(win_y_rel) * factors_y[i]))
     self.view["center"] = tuple(new_center)
Exemplo n.º 5
0
 def move_camera_by_screen(self, x_move, y_move, max_model_shift):
     """ move the camera acoording to a mouse movement
     @type x_move: int
     @value x_move: movement of the mouse along the x axis
     @type y_move: int
     @value y_move: movement of the mouse along the y axis
     @type max_model_shift: float
     @value max_model_shift: maximum shifting of the model view (e.g. for
         x_move == screen width)
     """
     factors_x, factors_y = self._get_axes_vectors()
     width, height = self._get_screen_dimensions()
     # relation of x/y movement to the respective screen dimension
     win_x_rel = (-2 * x_move) / float(width) / math.sin(self.view["fovy"])
     win_y_rel = (-2 * y_move) / float(height) / math.sin(self.view["fovy"])
     # This code is completely arbitrarily based on trial-and-error for
     # finding a nice movement speed for all distances.
     # Anyone with a better approach should just fix this.
     distance_vector = self.get("distance")
     distance = float(sqrt(sum([dim ** 2 for dim in distance_vector])))
     win_x_rel *= math.cos(win_x_rel / distance) ** 20
     win_y_rel *= math.cos(win_y_rel / distance) ** 20
     # update the model position that should be centered on the screen
     old_center = self.view["center"]
     new_center = []
     for i in range(3):
         new_center.append(old_center[i] \
                 + max_model_shift * (number(win_x_rel) * factors_x[i] \
                 + number(win_y_rel) * factors_y[i]))
     self.view["center"] = tuple(new_center)
Exemplo n.º 6
0
 def __init__(self, x, y, z):
     self.id = Point.id
     Point.id += 1
     self.x = number(x)
     self.y = number(y)
     self.z = number(z)
     self.reset_cache()
Exemplo n.º 7
0
def get_rotation_matrix_axis_angle(rot_axis, rot_angle, use_radians=True):
    """ calculate rotation matrix for a normalized vector and an angle

    see http://mathworld.wolfram.com/RotationMatrix.html
    @type rot_axis: tuple(float)
    @value rot_axis: the vector describes the rotation axis. Its length should
        be 1.0 (normalized).
    @type rot_angle: float
    @value rot_angle: rotation angle (radiant)
    @rtype: tuple(tuple(float))
    @return: the roation matrix (3x3)
    """
    if not use_radians:
        rot_angle *= math.pi / 180
    sin = number(math.sin(rot_angle))
    cos = number(math.cos(rot_angle))
    return ((cos + rot_axis[0]*rot_axis[0]*(1-cos),
            rot_axis[0]*rot_axis[1]*(1-cos) - rot_axis[2]*sin,
            rot_axis[0]*rot_axis[2]*(1-cos) + rot_axis[1]*sin),
            (rot_axis[1]*rot_axis[0]*(1-cos) + rot_axis[2]*sin,
            cos + rot_axis[1]*rot_axis[1]*(1-cos),
            rot_axis[1]*rot_axis[2]*(1-cos) - rot_axis[0]*sin),
            (rot_axis[2]*rot_axis[0]*(1-cos) - rot_axis[1]*sin,
            rot_axis[2]*rot_axis[1]*(1-cos) + rot_axis[0]*sin,
            cos + rot_axis[2]*rot_axis[2]*(1-cos)))
Exemplo n.º 8
0
 def _get_axes_vectors(self):
     """calculate the model vectors along the screen's x and y axes"""
     # The "up" vector defines, in what proportion each axis of the model is
     # in line with the screen's y axis.
     v_up = self.view["up"]
     factors_y = (number(v_up[0]), number(v_up[1]), number(v_up[2]))
     # Calculate the proportion of each model axis according to the x axis of
     # the screen.
     distv = self.view["distance"]
     distv = pnormalized((distv[0], distv[1], distv[2]))
     factors_x = pnormalized(pcross(distv, (v_up[0], v_up[1], v_up[2])))
     return (factors_x, factors_y)
Exemplo n.º 9
0
 def _get_axes_vectors(self):
     """calculate the model vectors along the screen's x and y axes"""
     # The "up" vector defines, in what proportion each axis of the model is
     # in line with the screen's y axis.
     v_up = self.view["up"]
     factors_y = (number(v_up[0]), number(v_up[1]), number(v_up[2]))
     # Calculate the proportion of each model axis according to the x axis of
     # the screen.
     distv = self.view["distance"]
     distv = pnormalized((distv[0], distv[1], distv[2]))
     factors_x = pnormalized(pcross(distv, (v_up[0], v_up[1], v_up[2])))
     return (factors_x, factors_y)
Exemplo n.º 10
0
 def set_bounds(self, low=None, high=None):
     if not low is None:
         if len(low) != 3:
             raise ValueError, "lower bounds should be supplied as a " \
                     + "tuple/list of 3 items - but %d were given" % len(low)
         else:
             self.bounds_low = [number(value) for value in low]
     if not high is None:
         if len(high) != 3:
             raise ValueError, "upper bounds should be supplied as a " \
                     + "tuple/list of 3 items - but %d were given" \
                     % len(high)
         else:
             self.bounds_high = [number(value) for value in high]
Exemplo n.º 11
0
 def set_bounds(self, low=None, high=None):
     if not low is None:
         if len(low) != 3:
             raise ValueError, "lower bounds should be supplied as a " \
                     + "tuple/list of 3 items - but %d were given" % len(low)
         else:
             self.bounds_low = [number(value) for value in low]
     if not high is None:
         if len(high) != 3:
             raise ValueError, "upper bounds should be supplied as a " \
                     + "tuple/list of 3 items - but %d were given" \
                     % len(high)
         else:
             self.bounds_high = [number(value) for value in high]
Exemplo n.º 12
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 caluclating " \
                        + "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 low, high
Exemplo n.º 13
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 caluclating " \
                        + "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 low, high
Exemplo n.º 14
0
 def __init__(self, radius, location=None, height=None):
     super(BaseCutter, self).__init__()
     if location is None:
         location = (0, 0, 0)
     if height is None:
         height = 10
     radius = number(radius)
     self.height = number(height)
     self.radius = radius
     self.radiussq = radius**2
     self.required_distance = 0
     self.distance_radius = self.radius
     self.distance_radiussq = self.distance_radius**2
     self.shape = {}
     self.location = location
     self.moveto(self.location)
     self.uuid = None
     self.update_uuid()
Exemplo n.º 15
0
 def __init__(self, radius, location=None, height=None):
     super(BaseCutter, self).__init__()
     if location is None:
         location = Point(0, 0, 0)
     if height is None:
         height = 10
     radius = number(radius)
     self.height = number(height)
     self.radius = radius
     self.radiussq = radius ** 2
     self.required_distance = 0
     self.distance_radius = self.radius
     self.distance_radiussq = self.distance_radius ** 2
     self.shape = {}
     self.location = location
     self.moveto(self.location)
     self.uuid = None
     self.update_uuid()
Exemplo n.º 16
0
 def get_machine_movement_distance(self, safety_height=0.0):
     result = 0
     safety_height = number(safety_height)
     current_position = None
     # go through all points of the path
     for new_pos, rapid in self.get_moves(safety_height):
         if not current_position is None:
             result += new_pos.sub(current_position).norm
         current_position = new_pos
     return result
Exemplo n.º 17
0
 def get_machine_movement_distance(self, safety_height=0.0):
     result = 0
     safety_height = number(safety_height)
     current_position = None
     # go through all points of the path
     for new_pos, rapid in self.get_moves(safety_height):
         if not current_position is None:
             result += new_pos.sub(current_position).norm
         current_position = new_pos
     return result
Exemplo n.º 18
0
    def get_machine_time(self, safety_height=0.0):
        """ calculate an estimation of the time required for processing the
        toolpath with the machine

        @value safety_height: the safety height configured for this toolpath
        @type safety_height: float
        @rtype: float
        @returns: the machine time used for processing the toolpath in minutes
        """
        result = 0
        feedrate = self.toolpath_settings.get_tool_settings()["feedrate"]
        feedrate = number(feedrate)
        safety_height = number(safety_height)
        current_position = None
        # go through all points of the path
        for new_pos, rapid in self.get_moves(safety_height):
            if not current_position is None:
                result += new_pos.sub(current_position).norm / feedrate
            current_position = new_pos
        return result
Exemplo n.º 19
0
def multiply_vector_matrix(v, m):
    """ Multiply a 3d vector with a 3x3 matrix. The result is a 3d vector.

    @type v: tuple(float) | list(float)
    @value v: a 3d vector as tuple or list containing three floats
    @type m: tuple(tuple(float)) | list(list(float))
    @value m: a 3x3 list/tuple of floats
    @rtype: tuple(float)
    @return: a tuple of 3 floats as the matrix product
    """
    if len(m) == 9:
        m = [number(value) for value in m]
        m = ((m[0], m[1], m[2]), (m[3], m[4], m[5]), (m[6], m[7], m[8]))
    else:
        new_m = []
        for column in m:
            new_m.append([number(value) for value in column])
    v = [number(value) for value in v]
    return (v[0] * m[0][0] + v[1] * m[0][1] + v[2] * m[0][2],
            v[0] * m[1][0] + v[1] * m[1][1] + v[2] * m[1][2],
            v[0] * m[2][0] + v[1] * m[2][1] + v[2] * m[2][2])
Exemplo n.º 20
0
def multiply_vector_matrix(v, m):
    """ Multiply a 3d vector with a 3x3 matrix. The result is a 3d vector.

    @type v: tuple(float) | list(float)
    @value v: a 3d vector as tuple or list containing three floats
    @type m: tuple(tuple(float)) | list(list(float))
    @value m: a 3x3 list/tuple of floats
    @rtype: tuple(float)
    @return: a tuple of 3 floats as the matrix product
    """
    if len(m) == 9:
        m = [number(value) for value in m]
        m = ((m[0], m[1], m[2]), (m[3], m[4], m[5]), (m[6], m[7], m[8]))
    else:
        new_m = []
        for column in m:
            new_m.append([number(value) for value in column])
    v = [number(value) for value in v]
    return (v[0] * m[0][0] + v[1] * m[0][1] + v[2] * m[0][2],
            v[0] * m[1][0] + v[1] * m[1][1] + v[2] * m[1][2],
            v[0] * m[2][0] + v[1] * m[2][1] + v[2] * m[2][2])
Exemplo n.º 21
0
def get_support_grid(minx,
                     maxx,
                     miny,
                     maxy,
                     z_plane,
                     dist_x,
                     dist_y,
                     thickness,
                     height,
                     offset_x=0.0,
                     offset_y=0.0,
                     adjustments_x=None,
                     adjustments_y=None):
    lines_x, lines_y = get_support_grid_locations(minx, maxx, miny, maxy,
                                                  dist_x, dist_y, offset_x,
                                                  offset_y, adjustments_x,
                                                  adjustments_y)
    # create all x grid lines
    grid_model = Model()
    # convert all inputs to "number"
    thickness = number(thickness)
    height = number(height)
    # helper variables
    thick_half = thickness / 2
    length_extension = max(thickness, height)
    for line_x in lines_x:
        # we make the grid slightly longer (by thickness) than necessary
        grid_model += _add_aligned_cuboid_to_model(line_x - thick_half,
                                                   line_x + thick_half,
                                                   miny - length_extension,
                                                   maxy + length_extension,
                                                   z_plane, z_plane + height)
    for line_y in lines_y:
        # we make the grid slightly longer (by thickness) than necessary
        grid_model += _add_aligned_cuboid_to_model(minx - length_extension,
                                                   maxx + length_extension,
                                                   line_y - thick_half,
                                                   line_y + thick_half,
                                                   z_plane, z_plane + height)
    return grid_model
Exemplo n.º 22
0
 def set_drill(self, shape, position):
     #geom = ode.GeomTransform(self._space)
     #geom.setOffset(position)
     #geom.setGeom(shape)
     #shape.setOffset(position)
     self._space.add(shape)
     # sadly PyODE forgets to update the "space" attribute that we need in
     # the cutters' "extend" functions
     shape.space = self._space
     self._add_geom(shape, position, append=False)
     self._drill_offset = [number(value) for value in position]
     self._drill = shape
     self.reset_drill()
     self._dirty = True
Exemplo n.º 23
0
 def set_drill(self, shape, position):
     #geom = ode.GeomTransform(self._space)
     #geom.setOffset(position)
     #geom.setGeom(shape)
     #shape.setOffset(position)
     self._space.add(shape)
     # sadly PyODE forgets to update the "space" attribute that we need in
     # the cutters' "extend" functions
     shape.space = self._space
     self._add_geom(shape, position, append=False)
     self._drill_offset = [number(value) for value in position]
     self._drill = shape
     self.reset_drill()
     self._dirty = True
Exemplo n.º 24
0
 def __init__(self, radius, minorradius, **kwargs):
     minorradius = number(minorradius)
     self.minorradius = minorradius
     # we need "minorradius" for "moveto" - thus set it before parent's init
     BaseCutter.__init__(self, radius, **kwargs)
     self.majorradius = self.radius - minorradius
     self.axis = (0, 0, 1)
     self.majorradiussq = self.majorradius**2
     self.minorradiussq = self.minorradius**2
     self.distance_majorradius = self.majorradius \
             + self.get_required_distance()
     self.distance_minorradius = self.minorradius \
             + self.get_required_distance()
     self.distance_majorradiussq = self.distance_majorradius**2
     self.distance_minorradiussq = self.distance_minorradius**2
Exemplo n.º 25
0
 def __init__(self, radius, minorradius, **kwargs):
     minorradius = number(minorradius)
     self.minorradius = minorradius
     # we need "minorradius" for "moveto" - thus set it before parent's init
     BaseCutter.__init__(self, radius, **kwargs)
     self.majorradius = self.radius - minorradius
     self.axis = (0, 0, 1)
     self.majorradiussq = self.majorradius ** 2
     self.minorradiussq = self.minorradius ** 2
     self.distance_majorradius = self.majorradius \
             + self.get_required_distance()
     self.distance_minorradius = self.minorradius \
             + self.get_required_distance()
     self.distance_majorradiussq = self.distance_majorradius ** 2
     self.distance_minorradiussq = self.distance_minorradius ** 2
Exemplo n.º 26
0
def get_support_grid(minx, maxx, miny, maxy, z_plane, dist_x, dist_y, thickness,
        height, offset_x=0.0, offset_y=0.0, adjustments_x=None,
        adjustments_y=None):
    lines_x, lines_y = get_support_grid_locations(minx, maxx, miny, maxy,
            dist_x, dist_y, offset_x, offset_y, adjustments_x, adjustments_y)
    # create all x grid lines
    grid_model = Model()
    # convert all inputs to "number"
    thickness = number(thickness)
    height = number(height)
    # helper variables
    thick_half = thickness / 2
    length_extension = max(thickness, height)
    for line_x in lines_x:
        # we make the grid slightly longer (by thickness) than necessary
        grid_model += _add_aligned_cuboid_to_model(line_x - thick_half,
                line_x + thick_half, miny - length_extension,
                maxy + length_extension, z_plane, z_plane + height)
    for line_y in lines_y:
        # we make the grid slightly longer (by thickness) than necessary
        grid_model += _add_aligned_cuboid_to_model(minx - length_extension,
                maxx + length_extension, line_y - thick_half,
                line_y + thick_half, z_plane, z_plane + height)
    return grid_model
Exemplo n.º 27
0
 def auto_adjust_distance(self):
     s = self.core
     v = self.view
     # adjust the distance to get a view of the whole object
     low_high = zip(*self._get_low_high_dims())
     if (None, None) in low_high:
         return
     max_dim = max([high - low for low, high in low_high])
     distv = pnormalized((v["distance"][0], v["distance"][1], v["distance"][2]))
     # The multiplier "1.25" is based on experiments. 1.414 (sqrt(2)) should
     # be roughly sufficient for showing the diagonal of any model.
     distv = pmul(distv, (max_dim * 1.25) / number(math.sin(v["fovy"] / 2)))
     self.view["distance"] = distv
     # Adjust the "far" distance for the camera to make sure, that huge
     # models (e.g. x=1000) are still visible.
     self.view["zfar"] = 100 * max_dim
Exemplo n.º 28
0
 def auto_adjust_distance(self):
     s = self.settings
     v = self.view
     # adjust the distance to get a view of the whole object
     dimx = s.get("maxx") - s.get("minx")
     dimy = s.get("maxy") - s.get("miny")
     dimz = s.get("maxz") - s.get("minz")
     max_dim = max(max(dimx, dimy), dimz)
     distv = Point(v["distance"][0], v["distance"][1], v["distance"][2]).normalized()
     # The multiplier "1.25" is based on experiments. 1.414 (sqrt(2)) should
     # be roughly sufficient for showing the diagonal of any model.
     distv = distv.mul((max_dim * 1.25) / number(math.sin(v["fovy"] / 2)))
     self.view["distance"] = (distv.x, distv.y, distv.z)
     # Adjust the "far" distance for the camera to make sure, that huge
     # models (e.g. x=1000) are still visible.
     self.view["zfar"] = 100 * max_dim
Exemplo n.º 29
0
 def auto_adjust_distance(self):
     s = self.settings
     v = self.view
     # adjust the distance to get a view of the whole object
     dimx = s.get("maxx") - s.get("minx")
     dimy = s.get("maxy") - s.get("miny")
     dimz = s.get("maxz") - s.get("minz")
     max_dim = max(max(dimx, dimy), dimz)
     distv = Point(v["distance"][0], v["distance"][1],
             v["distance"][2]).normalized()
     # The multiplier "1.25" is based on experiments. 1.414 (sqrt(2)) should
     # be roughly sufficient for showing the diagonal of any model.
     distv = distv.mul((max_dim * 1.25) / number(math.sin(v["fovy"] / 2)))
     self.view["distance"] = (distv.x, distv.y, distv.z)
     # Adjust the "far" distance for the camera to make sure, that huge
     # models (e.g. x=1000) are still visible.
     self.view["zfar"] = 100 * max_dim
Exemplo n.º 30
0
 def auto_adjust_distance(self):
     s = self.core
     v = self.view
     # adjust the distance to get a view of the whole object
     low_high = zip(*self._get_low_high_dims())
     if (None, None) in low_high:
         return
     max_dim = max([high - low for low, high in low_high])
     distv = pnormalized(
         (v["distance"][0], v["distance"][1], v["distance"][2]))
     # The multiplier "1.25" is based on experiments. 1.414 (sqrt(2)) should
     # be roughly sufficient for showing the diagonal of any model.
     distv = pmul(distv, (max_dim * 1.25) / number(math.sin(v["fovy"] / 2)))
     self.view["distance"] = distv
     # Adjust the "far" distance for the camera to make sure, that huge
     # models (e.g. x=1000) are still visible.
     self.view["zfar"] = 100 * max_dim
Exemplo n.º 31
0
def get_support_grid_locations(minx,
                               maxx,
                               miny,
                               maxy,
                               dist_x,
                               dist_y,
                               offset_x=0.0,
                               offset_y=0.0,
                               adjustments_x=None,
                               adjustments_y=None):
    def get_lines(center, dist, min_value, max_value):
        """ generate a list of positions starting from the middle going up and
        and down
        """
        if dist > 0:
            lines = [center]
            current = center
            while current - dist > min_value:
                current -= dist
                lines.insert(0, current)
            current = center
            while current + dist < max_value:
                current += dist
                lines.append(current)
        else:
            lines = []
        # remove lines that are out of range (e.g. due to a huge offset)
        lines = [line for line in lines if min_value < line < max_value]
        return lines

    # convert all inputs to the type defined in "number"
    dist_x = number(dist_x)
    dist_y = number(dist_y)
    offset_x = number(offset_x)
    offset_y = number(offset_y)
    center_x = (maxx + minx) / 2 + offset_x
    center_y = (maxy + miny) / 2 + offset_y
    lines_x = get_lines(center_x, dist_x, minx, maxx)
    lines_y = get_lines(center_y, dist_y, miny, maxy)
    if adjustments_x:
        for index in range(min(len(lines_x), len(adjustments_x))):
            lines_x[index] += number(adjustments_x[index])
    if adjustments_y:
        for index in range(min(len(lines_y), len(adjustments_y))):
            lines_y[index] += number(adjustments_y[index])
    return lines_x, lines_y
Exemplo n.º 32
0
def get_support_grid_locations(minx, maxx, miny, maxy, dist_x, dist_y,
        offset_x=0.0, offset_y=0.0, adjustments_x=None, adjustments_y=None):
    def get_lines(center, dist, min_value, max_value):
        """ generate a list of positions starting from the middle going up and
        and down
        """
        if dist > 0:
            lines = [center]
            current = center
            while current - dist > min_value:
                current -= dist
                lines.insert(0, current)
            current = center
            while current + dist < max_value:
                current += dist
                lines.append(current)
        else:
            lines = []
        # remove lines that are out of range (e.g. due to a huge offset)
        lines = [line for line in lines if min_value < line < max_value]
        return lines
    # convert all inputs to the type defined in "number"
    dist_x = number(dist_x)
    dist_y = number(dist_y)
    offset_x = number(offset_x)
    offset_y = number(offset_y)
    center_x = (maxx + minx) / 2 + offset_x
    center_y = (maxy + miny) / 2 + offset_y
    lines_x = get_lines(center_x, dist_x, minx, maxx)
    lines_y = get_lines(center_y, dist_y, miny, maxy)
    if adjustments_x:
        for index in range(min(len(lines_x), len(adjustments_x))):
            lines_x[index] += number(adjustments_x[index])
    if adjustments_y:
        for index in range(min(len(lines_y), len(adjustments_y))):
            lines_y[index] += number(adjustments_y[index])
    return lines_x, lines_y
Exemplo n.º 33
0
def pdiv(a, c):
    c = number(c)
    return (a[0] / c, a[1] / c, a[2] / c)
Exemplo n.º 34
0
 def mul(self, c):
     c = number(c)
     return Point(self.x * c, self.y * c, self.z * c)
Exemplo n.º 35
0
 def scale_distance(self, scale):
     if scale != 0:
         scale = number(scale)
         dist = self.view["distance"]
         self.view["distance"] = (scale * dist[0], scale * dist[1],
                 scale * dist[2])
Exemplo n.º 36
0
def generate_toolpath(model, tool_settings=None,
        bounds=None, direction="x",
        path_generator="DropCutter", path_postprocessor="ZigZagCutter",
        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,
        support_grid_offset_y=None, support_grid_adjustments_x=None,
        support_grid_adjustments_y=None, support_grid_average_distance=None,
        support_grid_minimum_bridges=None, support_grid_length=None,
        calculation_backend=None, callback=None):
    """ abstract interface for generating a toolpath

    @type model: pycam.Geometry.Model.Model
    @value model: a model contains surface triangles or a contour
    @type tool_settings: dict
    @value tool_settings: contains at least the following keys (depending on
        the tool type):
        "shape": any of possible cutter shape (see "pycam.Cutters")
        "tool_radius": main radius of the tools
        "torus_radius": (only for ToroidalCutter) second toroidal radius
    @type bounds_low: tuple(float) | list(float)
    @value bounds_low: the lower processing boundary (used for the center of
        the tool) (order: minx, miny, minz)
    @type bounds_high: tuple(float) | list(float)
    @value bounds_high: the lower processing boundary (used for the center of
        the tool) (order: maxx, maxy, maxz)
    @type direction: str
    @value direction: any member of the DIRECTIONS set (e.g. "x", "y" or "xy")
    @type path_generator: str
    @value path_generator: any member of the PATH_GENERATORS set
    @type path_postprocessor: str
    @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_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
    @value engrave_offset: toolpath distance to the contour model
    @type support_grid_distance_x: float
    @value support_grid_distance_x: distance between support grid lines along x
    @type support_grid_distance_y: float
    @value support_grid_distance_y: distance between support grid lines along y
    @type support_grid_thickness: float
    @value support_grid_thickness: thickness of the support grid
    @type support_grid_height: float
    @value support_grid_height: height of the support grid
    @type support_grid_offset_x: float
    @value support_grid_offset_x: shift the support grid by this value along x
    @type support_grid_offset_y: float
    @value support_grid_offset_y: shift the support grid by this value along y
    @type support_grid_adjustments_x: list(float)
    @value support_grid_adjustments_x: manual adjustment of each x-grid bar
    @type support_grid_adjustments_y: list(float)
    @value support_grid_adjustments_y: manual adjustment of each y-grid bar
    @type calculation_backend: str | None
    @value calculation_backend: any member of the CALCULATION_BACKENDS set
        The default is the triangular collision detection.
    @rtype: pycam.Toolpath.Toolpath | str
    @return: the resulting toolpath object or an error string in case of invalid
        arguments
    """
    log.debug("Starting toolpath generation")
    step_down = number(step_down)
    engrave_offset = number(engrave_offset)
    if bounds is None:
        # no bounds were given - we use the boundaries of the model
        bounds = pycam.Toolpath.Bounds(pycam.Toolpath.Bounds.TYPE_CUSTOM,
                (model.minx, model.miny, model.minz),
                (model.maxx, model.maxy, model.maxz))
    bounds_low, bounds_high = bounds.get_absolute_limits()
    minx, miny, minz = [number(value) for value in bounds_low]
    maxx, maxy, maxz = [number(value) for value in bounds_high]
    # trimesh model or contour model?
    if isinstance(model, pycam.Geometry.Model.ContourModel):
        # contour model
        trimesh_models = []
        contour_model = model
    else:
        # trimesh model
        trimesh_models = [model]
        contour_model = None
    # 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) \
            or (not support_grid_distance_y is None)) \
            and (not support_grid_thickness is None)):
        # grid height defaults to the thickness
        if support_grid_height is None:
            support_grid_height = support_grid_thickness
        if (support_grid_distance_x < 0) or (support_grid_distance_y < 0):
            return "The distance of the support grid must be a positive value"
        if not ((support_grid_distance_x > 0) or (support_grid_distance_y > 0)):
            return "Both distance values for the support grid may not be " \
                    + "zero at the same time"
        if support_grid_thickness <= 0:
            return "The thickness of the support grid must be a positive value"
        if support_grid_height <= 0:
            return "The height of the support grid must be a positive value"
        if not callback is None:
            callback(text="Preparing support grid model ...")
        support_grid_model = pycam.Toolpath.SupportGrid.get_support_grid(
                minx, maxx, miny, maxy, minz, support_grid_distance_x,
                support_grid_distance_y, support_grid_thickness,
                support_grid_height, offset_x=support_grid_offset_x,
                offset_y=support_grid_offset_y,
                adjustments_x=support_grid_adjustments_x,
                adjustments_y=support_grid_adjustments_y)
        trimesh_models.append(support_grid_model)
    elif (support_grid_type in ("distributed_edges", "distributed_corners")) \
            and (not support_grid_average_distance is None) \
            and (not support_grid_thickness is None) \
            and (not support_grid_length is None):
        if support_grid_height is None:
            support_grid_height = support_grid_thickness
        if support_grid_minimum_bridges is None:
            support_grid_minimum_bridges = 2
        if support_grid_average_distance <= 0:
            return "The average support grid distance must be a positive value"
        if support_grid_minimum_bridges <= 0:
            return "The minimum number of bridged per polygon must be a " \
                    + "positive value"
        if support_grid_thickness <= 0:
            return "The thickness of the support grid must be a positive value"
        if support_grid_height <= 0:
            return "The height of the support grid must be a positive value"
        if not callback is None:
            callback(text="Preparing support grid model ...")
        # check which model to choose
        if contour_model:
            model = contour_model
        else:
            model = trimesh_models[0]
        start_at_corners = (support_grid_type == "distributed_corners")
        support_grid_model = pycam.Toolpath.SupportGrid.get_support_distributed(
                model, minz, support_grid_average_distance,
                support_grid_minimum_bridges, support_grid_thickness,
                support_grid_height, support_grid_length,
                bounds, start_at_corners=start_at_corners)
        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 contour_model and (engrave_offset != 0):
        if not callback is None:
            callback(text="Preparing contour model with offset ...")
        contour_model = contour_model.get_offset_model(engrave_offset,
                callback=callback)
        if not contour_model:
            return "Failed to calculate offset polygons"
        if not callback is None:
            # reset percentage counter after the contour model calculation
            callback(percent=0)
            if callback(text="Checking contour model with offset for " \
                    + "collisions ..."):
                # quit requested
                return None
            progress_callback = ProgressCounter(
                    len(contour_model.get_polygons()), callback).increment
        else:
            progress_callback = None
        result = contour_model.check_for_collisions(callback=progress_callback)
        if result is None:
            return None
        elif result:
            warning = "The contour model contains colliding line groups. " + \
                    "This can cause problems with an engraving offset.\n" + \
                    "A collision was detected at (%.2f, %.2f, %.2f)." % \
                    (result.x, result.y, result.z)
            log.warning(warning)
        else:
            # no collisions and no user interruption
            pass
    # check the pocketing type
    if contour_model and (pocketing_type != "none"):
        if not callback is None:
            callback(text="Generating pocketing 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:
            if callback and callback():
                return "Interrupted"
            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):
                if callback and callback():
                    return "Interrupted"
                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:
        # use minz/maxz of the contour model (in other words: ignore z)
        contour_model = contour_model.get_cropped_model(minx, maxx, miny, maxy,
                contour_model.minz, contour_model.maxz)
        if not contour_model:
            return "No part of the contour model is within the bounding box."
    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)
    if isinstance(generator, basestring):
        return generator
    overlap = overlap_percent / 100.0
    if (overlap < 0) or (overlap >= 1):
        return "Invalid overlap value (%f): should be greater or equal 0 " \
                + "and lower than 1"
    # factor "2" since we are based on radius instead of diameter
    line_stepping = 2 * number(tool_settings["tool_radius"]) * (1 - overlap)
    if path_generator == "PushCutter":
        step_width = None
    else:
        # the step_width is only used for the DropCutter
        step_width = tool_settings["tool_radius"] / 4
    if path_generator == "DropCutter":
        layer_distance = None
    else:
        layer_distance = step_down
    direction_dict = {"x": pycam.Toolpath.MotionGrid.GRID_DIRECTION_X,
            "y": pycam.Toolpath.MotionGrid.GRID_DIRECTION_Y,
            "xy": pycam.Toolpath.MotionGrid.GRID_DIRECTION_XY}
    milling_style_grid = {
            "ignore": pycam.Toolpath.MotionGrid.MILLING_STYLE_IGNORE,
            "conventional": pycam.Toolpath.MotionGrid.MILLING_STYLE_CONVENTIONAL,
            "climb": pycam.Toolpath.MotionGrid.MILLING_STYLE_CLIMB}
    if path_generator in ("DropCutter", "PushCutter"):
        motion_grid = pycam.Toolpath.MotionGrid.get_fixed_grid(bounds,
                layer_distance, line_stepping, step_width=step_width,
                grid_direction=direction_dict[direction],
                milling_style=milling_style_grid[milling_style])
        if path_generator == "DropCutter":
            toolpath = generator.GenerateToolPath(motion_grid, minz, maxz,
                    callback)
        else:
            toolpath = generator.GenerateToolPath(motion_grid, callback)
    elif path_generator == "EngraveCutter":
        if step_down > 0:
            dz = step_down
        else:
            dz = maxz - minz
        toolpath = generator.GenerateToolPath(minz, maxz, step_width, dz,
                callback)
    elif path_generator == "ContourFollow":
        if step_down > 0:
            dz = step_down
        else:
            dz = maxz - minz
            if dz <= 0:
                dz = 1
        toolpath = generator.GenerateToolPath(minx, maxx, miny, maxy, minz,
                maxz, dz, callback)
    elif path_generator == "Contour2dCutter": # JULIEN
        toolpath = generator.GenerateToolPath(callback)
    else:
        return "Invalid path generator (%s): not one of %s" \
                % (path_generator, PATH_GENERATORS)
    return toolpath
Exemplo n.º 37
0
def pmul(a, c):
    c = number(c)
    return (a[0] * c, a[1] * c, a[2] * c)
Exemplo n.º 38
0
def pmul(a, c):
    c = number(c)
    return (a[0] * c, a[1] * c, a[2] * c)
Exemplo n.º 39
0
 def div(self, c):
     c = number(c)
     return Point(self.x / c, self.y / c, self.z / c)
Exemplo n.º 40
0
 def div(self, c):
     c = number(c)
     return Point(self.x / c, self.y / c, self.z / c)
Exemplo n.º 41
0
 def set_required_distance(self, value):
     if value >= 0:
         self.required_distance = number(value)
         self.distance_radius = self.radius + self.get_required_distance()
         self.distance_radiussq = self.distance_radius * self.distance_radius
         self.update_uuid()
Exemplo n.º 42
0
def generate_toolpath(model,
                      tool_settings=None,
                      bounds=None,
                      direction="x",
                      path_generator="DropCutter",
                      path_postprocessor="ZigZagCutter",
                      material_allowance=0,
                      overlap_percent=0,
                      step_down=0,
                      engrave_offset=0,
                      milling_style="ignore",
                      pocketing_type="none",
                      support_model=None,
                      calculation_backend=None,
                      callback=None):
    """ abstract interface for generating a toolpath

    @type model: pycam.Geometry.Model.Model
    @value model: a model contains surface triangles or a contour
    @type tool_settings: dict
    @value tool_settings: contains at least the following keys (depending on
        the tool type):
        "shape": any of possible cutter shape (see "pycam.Cutters")
        "tool_radius": main radius of the tools
        "torus_radius": (only for ToroidalCutter) second toroidal radius
    @type bounds_low: tuple(float) | list(float)
    @value bounds_low: the lower processing boundary (used for the center of
        the tool) (order: minx, miny, minz)
    @type bounds_high: tuple(float) | list(float)
    @value bounds_high: the lower processing boundary (used for the center of
        the tool) (order: maxx, maxy, maxz)
    @type direction: str
    @value direction: any member of the DIRECTIONS set (e.g. "x", "y" or "xy")
    @type path_generator: str
    @value path_generator: any member of the PATH_GENERATORS set
    @type path_postprocessor: str
    @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_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
    @value engrave_offset: toolpath distance to the contour model
    @type calculation_backend: str | None
    @value calculation_backend: any member of the CALCULATION_BACKENDS set
        The default is the triangular collision detection.
    @rtype: pycam.Toolpath.Toolpath | str
    @return: the resulting toolpath object or an error string in case of invalid
        arguments
    """
    log.debug("Starting toolpath generation")
    step_down = number(step_down)
    engrave_offset = number(engrave_offset)
    if bounds is None:
        # no bounds were given - we use the boundaries of the model
        bounds = pycam.Toolpath.Bounds(pycam.Toolpath.Bounds.TYPE_CUSTOM,
                                       (model.minx, model.miny, model.minz),
                                       (model.maxx, model.maxy, model.maxz))
    bounds_low, bounds_high = bounds.get_absolute_limits()
    minx, miny, minz = [number(value) for value in bounds_low]
    maxx, maxy, maxz = [number(value) for value in bounds_high]
    # trimesh model or contour model?
    if isinstance(model, pycam.Geometry.Model.ContourModel):
        # contour model
        trimesh_models = []
        contour_model = model
    else:
        # trimesh model
        trimesh_models = [model]
        contour_model = None
    # 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_model:
        trimesh_models.append(support_model)
    # Adapt the contour_model to the engraving offset. This offset is
    # considered to be part of the material_allowance.
    if contour_model and (engrave_offset != 0):
        if not callback is None:
            callback(text="Preparing contour model with offset ...")
        contour_model = contour_model.get_offset_model(engrave_offset,
                                                       callback=callback)
        if contour_model:
            return "Failed to calculate offset polygons"
        if not callback is None:
            # reset percentage counter after the contour model calculation
            callback(percent=0)
            if callback(text="Checking contour model with offset for " \
                    + "collisions ..."):
                # quit requested
                return None
            progress_callback = ProgressCounter(
                len(contour_model.get_polygons()), callback).increment
        else:
            progress_callback = None
        result = contour_model.check_for_collisions(callback=progress_callback)
        if result is None:
            return None
        elif result:
            warning = "The contour model contains colliding line groups. " + \
                    "This can cause problems with an engraving offset.\n" + \
                    "A collision was detected at (%.2f, %.2f, %.2f)." % \
                    (result.x, result.y, result.z)
            log.warning(warning)
        else:
            # no collisions and no user interruption
            pass
    # check the pocketing type
    if contour_model and (pocketing_type != "none"):
        if not callback is None:
            callback(text="Generating pocketing 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:
            if callback and callback():
                return "Interrupted"
            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):
                if callback and callback():
                    return "Interrupted"
                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:
        # use minz/maxz of the contour model (in other words: ignore z)
        contour_model = contour_model.get_cropped_model(
            minx, maxx, miny, maxy, contour_model.minz, contour_model.maxz)
        if contour_model:
            return "No part of the contour model is within the bounding box."
    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)
    if isinstance(generator, basestring):
        return generator
    overlap = overlap_percent / 100.0
    if (overlap < 0) or (overlap >= 1):
        return "Invalid overlap value (%f): should be greater or equal 0 " \
                + "and lower than 1"
    # factor "2" since we are based on radius instead of diameter
    line_stepping = 2 * number(tool_settings["tool_radius"]) * (1 - overlap)
    if path_generator == "PushCutter":
        step_width = None
    else:
        # the step_width is only used for the DropCutter
        step_width = tool_settings["tool_radius"] / 4
    if path_generator == "DropCutter":
        layer_distance = None
    else:
        layer_distance = step_down
    direction_dict = {
        "x": pycam.Toolpath.MotionGrid.GRID_DIRECTION_X,
        "y": pycam.Toolpath.MotionGrid.GRID_DIRECTION_Y,
        "xy": pycam.Toolpath.MotionGrid.GRID_DIRECTION_XY
    }
    milling_style_grid = {
        "ignore": pycam.Toolpath.MotionGrid.MILLING_STYLE_IGNORE,
        "conventional": pycam.Toolpath.MotionGrid.MILLING_STYLE_CONVENTIONAL,
        "climb": pycam.Toolpath.MotionGrid.MILLING_STYLE_CLIMB
    }
    if path_generator in ("DropCutter", "PushCutter"):
        motion_grid = pycam.Toolpath.MotionGrid.get_fixed_grid(
            (bounds_low, bounds_high),
            layer_distance,
            line_stepping,
            step_width=step_width,
            grid_direction=direction_dict[direction],
            milling_style=milling_style_grid[milling_style])
        if path_generator == "DropCutter":
            toolpath = generator.GenerateToolPath(motion_grid, minz, maxz,
                                                  callback)
        else:
            toolpath = generator.GenerateToolPath(motion_grid, callback)
    elif path_generator == "EngraveCutter":
        if step_down > 0:
            dz = step_down
        else:
            dz = maxz - minz
        toolpath = generator.GenerateToolPath(minz, maxz, step_width, dz,
                                              callback)
    elif path_generator == "ContourFollow":
        if step_down > 0:
            dz = step_down
        else:
            dz = maxz - minz
            if dz <= 0:
                dz = 1
        toolpath = generator.GenerateToolPath(minx, maxx, miny, maxy, minz,
                                              maxz, dz, callback)
    else:
        return "Invalid path generator (%s): not one of %s" \
                % (path_generator, PATH_GENERATORS)
    return toolpath
Exemplo n.º 43
0
 def __init__(self, x, y, z):
     super(Point, self).__init__()
     self.x = number(x)
     self.y = number(y)
     self.z = number(z)
     self.reset_cache()
Exemplo n.º 44
0
 def scale_distance(self, scale):
     if scale != 0:
         scale = number(scale)
         dist = self.view["distance"]
         self.view["distance"] = (scale * dist[0], scale * dist[1],
                                  scale * dist[2])
Exemplo n.º 45
0
 def mul(self, c):
     c = number(c)
     return Point(self.x * c, self.y * c, self.z * c)
Exemplo n.º 46
0
 def set_required_distance(self, value):
     if value >= 0:
         self.required_distance = number(value)
         self.distance_radius = self.radius + self.get_required_distance()
         self.distance_radiussq = self.distance_radius * self.distance_radius
         self.update_uuid()
Exemplo n.º 47
0
 def __init__(self, x, y, z):
     super(Point, self).__init__()
     self.x = number(x)
     self.y = number(y)
     self.z = number(z)
     self.reset_cache()
Exemplo n.º 48
0
def pdiv(a, c):
    c = number(c)
    return (a[0] / c, a[1] / c, a[2] / c)