Exemplo n.º 1
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 rotation 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.º 2
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.º 3
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):
    """ calculate positions of a orthogonal grid of support bridges

    @param minx: minimum x value of the target area
    @param maxx: maximum x value of the target area
    @param miny: minimum y value of the target area
    @param maxy: maximum y value of the target area
    @param dist_x: distance between two lines parallel to the y axis
    @param dist_y: distance between two lines parallel to the x axis
    @param adjustments_x: iterable of offsets to be added to each x position
    @param adjustments_y: iterable of offsets to be added to each y position
    """
    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)
    # apply as many offsets from adjustments_x and adjustments_y as available
    for value_list, adjustments in ((lines_x, adjustments_x), (lines_y,
                                                               adjustments_y)):
        if adjustments:
            for index, (current_value,
                        adjustment) in enumerate(zip(value_list, adjustments)):
                value_list[index] = current_value + number(adjustment)
    return lines_x, lines_y
Exemplo n.º 4
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.º 5
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))
Exemplo n.º 6
0
 def __init__(self, radius, location=None, height=None):
     super().__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.º 7
0
    def get_offset_polygons_incremental(self, offset, depth=20):
        if offset == 0:
            return [self]
        if offset in self._cached_offset_polygons:
            return self._cached_offset_polygons[offset]

        def is_better_offset(previous_offset, alternative_offset):
            return (((offset < alternative_offset < 0) or
                     (0 < alternative_offset < offset))
                    and (abs(alternative_offset) > abs(previous_offset)))

        # check the cache for a good starting point
        best_offset = 0
        best_offset_polygons = [self]
        for cached_offset in self._cached_offset_polygons:
            if is_better_offset(best_offset, cached_offset):
                best_offset = cached_offset
                best_offset_polygons = self._cached_offset_polygons[
                    cached_offset]
        remaining_offset = offset - best_offset
        result_polygons = []
        for poly in best_offset_polygons:
            result = poly.get_offset_polygons_validated(remaining_offset)
            if result is not None:
                result_polygons.extend(result)
            else:
                lower = number(0)
                upper = remaining_offset
                loop_limit = 90
                while (loop_limit > 0):
                    middle = (upper + lower) / 2
                    result = poly.get_offset_polygons_validated(middle)
                    if result is None:
                        upper = middle
                    else:
                        if depth > 0:
                            # the original polygon was split or modified
                            print("Next level: %s" % str(middle))
                            shifted_sub_polygons = []
                            for sub_poly in result:
                                shifted_sub_polygons.extend(
                                    sub_poly.get_offset_polygons(
                                        remaining_offset - middle,
                                        depth=depth - 1))
                            result_polygons.extend(shifted_sub_polygons)
                            break
                        else:
                            print("Maximum recursion level reached")
                            break
                    loop_limit -= 1
                else:
                    # no split event happened -> no valid shifted polygon
                    pass
        self._cached_offset_polygons[offset] = result_polygons
        return result_polygons
Exemplo n.º 8
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.º 9
0
def get_support_grid(minx,
                     maxx,
                     miny,
                     maxy,
                     z_plane,
                     dist_x,
                     dist_y,
                     thickness,
                     height,
                     length,
                     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
    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,
                                                   maxy + length, 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,
                                                   maxx + length,
                                                   line_y - thick_half,
                                                   line_y + thick_half,
                                                   z_plane, z_plane + height)
    return grid_model
Exemplo n.º 10
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.º 11
0
 def auto_adjust_distance(self):
     v = self.view
     # adjust the distance to get a view of the whole object
     low_high = list(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.º 12
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.º 13
0
def pdiv(a, c):
    c = number(c)
    return (a[0] / c, a[1] / c, a[2] / c)
Exemplo n.º 14
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.º 15
0
def pmul(a, c):
    c = number(c)
    return (a[0] * c, a[1] * c, a[2] * c)
Exemplo n.º 16
0
def pmul(a, c):
    c = number(c)
    if not a:
        return (False, False, False)
    return (a[0] * c, a[1] * c, a[2] * c)
Exemplo n.º 17
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.º 18
0
    def get_offset_polygons(self, offset, callback=None):
        def simplify_polygon_intersections(lines):
            new_group = lines[:]
            # remove all non-adjacent intersecting lines (this splits the group)
            if len(new_group) > 0:
                group_starts = []
                index1 = 0
                while index1 < len(new_group):
                    index2 = 0
                    while index2 < len(new_group):
                        index_distance = min(
                            abs(index2 - index1),
                            abs(len(new_group) - (index2 - index1)))
                        # skip neighbours
                        if index_distance > 1:
                            line1 = new_group[index1]
                            line2 = new_group[index2]
                            intersection, factor = line1.get_intersection(
                                line2)
                            if intersection and (pdist(intersection, line1.p1) > epsilon) \
                                    and (pdist(intersection, line1.p2) > epsilon):
                                del new_group[index1]
                                new_group.insert(index1,
                                                 Line(line1.p1, intersection))
                                new_group.insert(index1 + 1,
                                                 Line(intersection, line1.p2))
                                # Shift all items in "group_starts" by one if
                                # they reference a line whose index changed.
                                for i in range(len(group_starts)):
                                    if group_starts[i] > index1:
                                        group_starts[i] += 1
                                if index1 + 1 not in group_starts:
                                    group_starts.append(index1 + 1)
                                # don't update index2 -> maybe there are other hits
                            elif intersection and (pdist(
                                    intersection, line1.p1) < epsilon):
                                if index1 not in group_starts:
                                    group_starts.append(index1)
                                index2 += 1
                            else:
                                index2 += 1
                        else:
                            index2 += 1
                    index1 += 1
                # The lines intersect each other
                # We need to split the group.
                if len(group_starts) > 0:
                    group_starts.sort()
                    groups = []
                    last_start = 0
                    for group_start in group_starts:
                        transfer_group = new_group[last_start:group_start]
                        # add only non-empty groups
                        if transfer_group:
                            groups.append(transfer_group)
                        last_start = group_start

                    # Add the remaining lines to the first group or as a new
                    # group.
                    if groups[0][0].p1 == new_group[-1].p2:
                        groups[0] = new_group[last_start:] + groups[0]
                    else:
                        groups.append(new_group[last_start:])
                    # try to find open groups that can be combined
                    combined_groups = []
                    for index, current_group in enumerate(groups):
                        # Check if the group is not closed: try to add it to
                        # other non-closed groups.
                        if current_group[0].p1 == current_group[-1].p2:
                            # a closed group
                            combined_groups.append(current_group)
                        else:
                            # the current group is open
                            for other_group in groups[index + 1:]:
                                if other_group[0].p1 != other_group[-1].p2:
                                    # This group is also open - a candidate
                                    # for merging?
                                    if other_group[0].p1 == current_group[
                                            -1].p2:
                                        current_group.reverse()
                                        for line in current_group:
                                            other_group.insert(0, line)
                                        break
                                    if other_group[-1].p2 == current_group[
                                            0].p1:
                                        other_group.extend(current_group)
                                        break
                            else:
                                # not suitable open group found
                                combined_groups.append(current_group)
                    return combined_groups
                else:
                    # just return one group without intersections
                    return [new_group]
            else:
                return None

        offset = number(offset)
        if offset == 0:
            return [self]
        if self.is_outer():
            inside_shifting = max(0, -offset)
        else:
            inside_shifting = max(0, offset)
        if inside_shifting * 2 >= self.get_max_inside_distance():
            # This offset will not create a valid offset polygon.
            # Sadly there is currently no other way to detect a complete flip of
            # something like a circle.
            log.debug("Skipping offset polygon: polygon is too small")
            return []
        points = []
        for index in range(len(self._points)):
            points.append(self.get_shifted_vertex(index, offset))
        new_lines = []
        for index in range(len(points) - 1):
            p1 = points[index]
            p2 = points[(index + 1)]
            new_lines.append(Line(p1, p2))
        if self.is_closed and (len(points) > 1):
            new_lines.append(Line(points[-1], points[0]))
        if callback and callback():
            return None
        cleaned_line_groups = simplify_polygon_intersections(new_lines)
        if cleaned_line_groups is None:
            log.debug("Skipping offset polygon: intersections could not be "
                      "simplified")
            return None
        else:
            if not cleaned_line_groups:
                log.debug("Skipping offset polygon: no polygons left after "
                          "intersection simplification")
            groups = []
            for lines in cleaned_line_groups:
                if callback and callback():
                    return None
                group = Polygon(self.plane)
                for line in lines:
                    group.append(line)
                groups.append(group)
            if not groups:
                log.debug("Skipping offset polygon: toggled polygon removed")
            # remove all polygons that are within other polygons
            result = []
            for group in groups:
                inside = False
                for group_test in groups:
                    if callback and callback():
                        return None
                    if group_test is group:
                        continue
                    if group_test.is_polygon_inside(group):
                        inside = True
                if not inside:
                    result.append(group)
            if not result:
                log.debug(
                    "Skipping offset polygon: polygon is inside of another one"
                )
            return result