Ejemplo n.º 1
0
 def reset_cache(self):
     self.minx = min(self.p1[0], self.p2[0], self.p3[0])
     self.miny = min(self.p1[1], self.p2[1], self.p3[1])
     self.minz = min(self.p1[2], self.p2[2], self.p3[2])
     self.maxx = max(self.p1[0], self.p2[0], self.p3[0])
     self.maxy = max(self.p1[1], self.p2[1], self.p3[1])
     self.maxz = max(self.p1[2], self.p2[2], self.p3[2])
     self.e1 = Line(self.p1, self.p2)
     self.e2 = Line(self.p2, self.p3)
     self.e3 = Line(self.p3, self.p1)
     # calculate normal, if p1-p2-pe are in clockwise order
     if self.normal is None:
         self.normal = pnormalized(
             pcross(psub(self.p3, self.p1), psub(self.p2, self.p1)))
     if not len(self.normal) > 3:
         self.normal = (self.normal[0], self.normal[1], self.normal[2], 'v')
     self.center = pdiv(padd(padd(self.p1, self.p2), self.p3), 3)
     self.plane = Plane(self.center, self.normal)
     # calculate circumcircle (resulting in radius and middle)
     denom = pnorm(pcross(psub(self.p2, self.p1), psub(self.p3, self.p2)))
     self.radius = (pdist(self.p2, self.p1) * pdist(self.p3, self.p2) *
                    pdist(self.p3, self.p1)) / (2 * denom)
     self.radiussq = self.radius**2
     denom2 = 2 * denom * denom
     alpha = pdist_sq(self.p3, self.p2) * pdot(psub(
         self.p1, self.p2), psub(self.p1, self.p3)) / denom2
     beta = pdist_sq(self.p1, self.p3) * pdot(psub(
         self.p2, self.p1), psub(self.p2, self.p3)) / denom2
     gamma = pdist_sq(self.p1, self.p2) * pdot(psub(
         self.p3, self.p1), psub(self.p3, self.p2)) / denom2
     self.middle = (self.p1[0] * alpha + self.p2[0] * beta +
                    self.p3[0] * gamma, self.p1[1] * alpha +
                    self.p2[1] * beta + self.p3[1] * gamma,
                    self.p1[2] * alpha + self.p2[2] * beta +
                    self.p3[2] * gamma)
Ejemplo n.º 2
0
def intersect_sphere_line(center, radius, radiussq, direction, edge):
    # make a plane by sliding the line along the direction (1)
    d = edge.dir
    n = pcross(d, direction)
    if pnorm(n) == 0:
        # no contact point, but should check here if sphere *always* intersects
        # line...
        return (None, None, INFINITE)
    n = pnormalized(n)

    # calculate the distance from the sphere center to the plane
    dist = -pdot(center, n) + pdot(edge.p1, n)
    if abs(dist) > radius - epsilon:
        return (None, None, INFINITE)
    # this gives us the intersection circle on the sphere

    # now take a plane through the edge and perpendicular to the direction (2)
    # find the center on the circle closest to this plane

    # which means the other component is perpendicular to this plane (2)
    n2 = pnormalized(pcross(n, d))

    # the contact point is on a big circle through the sphere...
    dist2 = sqrt(radiussq - dist * dist)

    # ... and it's on the plane (1)
    ccp = padd(center, padd(pmul(n, dist), pmul(n2, dist2)))

    # now intersect a line through this point with the plane (2)
    plane = Plane(edge.p1, n2)
    (cp, l) = plane.intersect_point(direction, ccp)
    return (ccp, cp, l)
Ejemplo n.º 3
0
 def get_intersection(self, line, infinite_lines=False):
     """ Get the point of intersection between two lines. Intersections
     outside the length of these lines are ignored.
     Returns (None, None) if no valid intersection was found.
     Otherwise the result is (CollisionPoint, distance). Distance is between
     0 and 1.
     """
     x1, x2, x3, x4 = self.p1, self.p2, line.p1, line.p2
     a = psub(x2, x1)
     b = psub(x4, x3)
     c = psub(x3, x1)
     # see http://mathworld.wolfram.com/Line-LineIntersection.html (24)
     try:
         factor = pdot(pcross(c, b), pcross(a, b)) / pnormsq(pcross(a, b))
     except ZeroDivisionError:
         # lines are parallel
         # check if they are _one_ line
         if pnorm(pcross(a, c)) != 0:
             # the lines are parallel with a distance
             return None, None
         # the lines are on one straight
         candidates = []
         if self.is_point_inside(x3):
             candidates.append((x3, pnorm(c) / pnorm(a)))
         elif self.is_point_inside(x4):
             candidates.append((x4, pdist(line.p2, self.p1) / pnorm(a)))
         elif line.is_point_inside(x1):
             candidates.append((x1, 0))
         elif line.is_point_inside(x2):
             candidates.append((x2, 1))
         else:
             return None, None
         # return the collision candidate with the lowest distance
         candidates.sort(key=lambda collision: collision[1])
         return candidates[0]
     if infinite_lines or (-epsilon <= factor <= 1 + epsilon):
         intersec = padd(x1, pmul(a, factor))
         # check if the intersection is between x3 and x4
         if infinite_lines:
             return intersec, factor
         elif ((min(x3[0], x4[0]) - epsilon <= intersec[0] <=
                max(x3[0], x4[0]) + epsilon)
               and (min(x3[1], x4[1]) - epsilon <= intersec[1] <=
                    max(x3[1], x4[1]) + epsilon)
               and (min(x3[2], x4[2]) - epsilon <= intersec[2] <=
                    max(x3[2], x4[2]) + epsilon)):
             return intersec, factor
         else:
             # intersection outside of the length of line(x3, x4)
             return None, None
     else:
         # intersection outside of the length of line(x1, x2)
         return None, None
Ejemplo n.º 4
0
 def calculate_point_height(self, x, y, func):
     point = (x, y, self.outer.minz)
     if not self.outer.is_point_inside(point):
         return None
     for poly in self.inner:
         if poly.is_point_inside(point):
             return None
     point = (x, y, self.outer.minz)
     line_distances = []
     for line in self.lines:
         cross_product = pcross(line.dir, psub(point, line.p1))
         if cross_product[2] > 0:
             close_points = []
             close_point = line.closest_point(point)
             if not line.is_point_inside(close_point):
                 close_points.append(line.p1)
                 close_points.append(line.p2)
             else:
                 close_points.append(close_point)
             for p in close_points:
                 direction = psub(point, p)
                 dist = pnorm(direction)
                 line_distances.append(dist)
         elif cross_product[2] == 0:
             # the point is on the line
             line_distances.append(0.0)
             # no other line can get closer than this
             break
         else:
             # the point is in the left of this line
             pass
     line_distances.sort()
     return self.z_level + func(line_distances[0])
Ejemplo n.º 5
0
def intersect_torus_point(center, axis, majorradius, minorradius,
                          majorradiussq, minorradiussq, direction, point):
    dist = 0
    if (direction[0] == 0) and (direction[1] == 0):
        # drop
        minlsq = (majorradius - minorradius)**2
        maxlsq = (majorradius + minorradius)**2
        l_sq = (point[0] - center[0])**2 + (point[1] - center[1])**2
        if (l_sq < minlsq + epsilon) or (l_sq > maxlsq - epsilon):
            return (None, None, INFINITE)
        l_len = sqrt(l_sq)
        z_sq = minorradiussq - (majorradius - l_len)**2
        if z_sq < 0:
            return (None, None, INFINITE)
        z = sqrt(z_sq)
        ccp = (point[0], point[1], center[2] - z)
        dist = ccp[2] - point[2]
    elif direction[2] == 0:
        # push
        z = point[2] - center[2]
        if abs(z) > minorradius - epsilon:
            return (None, None, INFINITE)
        l_len = majorradius + sqrt(minorradiussq - z * z)
        n = pcross(axis, direction)
        d = pdot(n, point) - pdot(n, center)
        if abs(d) > l_len - epsilon:
            return (None, None, INFINITE)
        a = sqrt(l_len * l_len - d * d)
        ccp = padd(padd(center, pmul(n, d)), pmul(direction, a))
        ccp = (ccp[0], ccp[1], point[2])
        dist = pdot(psub(point, ccp), direction)
    else:
        # general case
        x = psub(point, center)
        v = pmul(direction, -1)
        x_x = pdot(x, x)
        x_v = pdot(x, v)
        x1 = (x[0], x[1], 0)
        v1 = (v[0], v[1], 0)
        x1_x1 = pdot(x1, x1)
        x1_v1 = pdot(x1, v1)
        v1_v1 = pdot(v1, v1)
        r2_major = majorradiussq
        r2_minor = minorradiussq
        a = 1.0
        b = 4 * x_v
        c = 2 * (x_x + 2 * x_v**2 +
                 (r2_major - r2_minor) - 2 * r2_major * v1_v1)
        d = 4 * (x_x * x_v + x_v *
                 (r2_major - r2_minor) - 2 * r2_major * x1_v1)
        e = ((x_x)**2 + 2 * x_x * (r2_major - r2_minor) +
             (r2_major - r2_minor)**2 - 4 * r2_major * x1_x1)
        r = poly4_roots(a, b, c, d, e)
        if not r:
            return (None, None, INFINITE)
        else:
            l_len = min(r)
        ccp = padd(point, pmul(direction, -l_len))
        dist = l_len
    return (ccp, point, dist)
Ejemplo n.º 6
0
def get_bisector(p1, p2, p3, up_vector):
    """ Calculate the bisector between p1, p2 and p3, whereas p2 is the origin
    of the angle.
    """
    d1 = pnormalized(psub(p2, p1))
    d2 = pnormalized(psub(p2, p3))
    bisector_dir = pnormalized(padd(d1, d2))
    if bisector_dir is None:
        # the two vectors pointed to opposite directions
        bisector_dir = pnormalized(pcross(d1, up_vector))
    else:
        skel_up_vector = pcross(bisector_dir, psub(p2, p1))
        if pdot(up_vector, skel_up_vector) < 0:
            # reverse the skeleton vector to point outwards
            bisector_dir = pmul(bisector_dir, -1)
    return bisector_dir
Ejemplo n.º 7
0
def _process_one_triangle(extra_args):
    model, cutter, up_vector, triangle, z = extra_args
    result = []
    # ignore triangles below the z level
    if triangle.maxz < z:
        # Case 1a
        return result, None
    # ignore triangles pointing upwards or downwards
    if pnorm(pcross(triangle.normal, up_vector)) == 0:
        # Case 1b
        return result, None
    edge_collisions = get_collision_waterline_of_triangle(
        model, cutter, up_vector, triangle, z)
    if edge_collisions is None:
        # don't try to use this edge again
        return result, [id(triangle)]
    elif len(edge_collisions) == 0:
        return result, None
    else:
        for cutter_location, edge in edge_collisions:
            shifted_edge = get_shifted_waterline(up_vector, edge,
                                                 cutter_location)
            if shifted_edge is not None:
                if _DEBUG_DISBALE_WATERLINE_SHIFT:
                    result.append((edge, edge))
                else:
                    result.append((edge, shifted_edge))
        return result, None
Ejemplo n.º 8
0
def get_x3d_cone(start: tuple, end: tuple, position: float, length: float, radius: float,
                 color: tuple):
    # by default the X3D cone points along the y axis
    original_vector = (0, 1, 0)
    line_vector = psub(end, start)
    line_normalized = pnormalized(line_vector)
    # the cone position is at its bottom
    bottom_position = padd(start, pmul(line_vector, position))
    # handling of corner cases
    if pnormsq(pcross(original_vector, line_normalized)) == 0:
        # Both vectors are aligned - but maybe point into different directions.
        rotation_axis = (1, 0, 0)
        rotation_angle = math.pi if original_vector != line_normalized else 0
    else:
        # Both vectors are not aligned.  Use a normal rotation.
        # Rotate around the vector in the vector in the middle by 180 degrees.
        rotation_axis = padd(original_vector, line_normalized)
        rotation_angle = math.pi
    yield ('<Transform translation="{:f} {:f} {:f}" rotation="{:f} {:f} {:f} {:f}">'
           .format(*bottom_position, *rotation_axis, rotation_angle))
    yield "<Shape>"
    yield "<Appearance>"
    yield ('<Material diffuseColor="{:f} {:f} {:f}" transparency="{:f}" />'
           .format(color["red"], color["green"], color["blue"], 1 - color["alpha"]))
    yield "</Appearance>"
    yield '<Cone bottomRadius="{:f}" topRadius="0" height="{:f}" />'.format(radius, length)
    yield "</Shape>"
    yield "</Transform>"
Ejemplo n.º 9
0
def _get_edge_bridges(polygon, z_plane, min_bridges, average_distance,
                      avoid_distance):
    def is_near_list(point_list, point, distance):
        for p in point_list:
            if pdist(p, point) <= distance:
                return True
        return False

    lines = polygon.get_lines()
    poly_lengths = polygon.get_lengths()
    num_of_bridges = max(min_bridges,
                         int(round(sum(poly_lengths) / average_distance)))
    real_average_distance = sum(poly_lengths) / num_of_bridges
    max_line_index = poly_lengths.index(max(poly_lengths))
    positions = []
    current_line_index = max_line_index
    distance_processed = poly_lengths[current_line_index] / 2
    positions.append(current_line_index)
    while len(positions) < num_of_bridges:
        current_line_index += 1
        current_line_index %= len(poly_lengths)
        # skip lines that are not at least twice as long as the grid width
        while (distance_processed + poly_lengths[current_line_index] <
               real_average_distance):
            distance_processed += poly_lengths[current_line_index]
            current_line_index += 1
            current_line_index %= len(poly_lengths)
        positions.append(current_line_index)
        distance_processed += poly_lengths[current_line_index]
        distance_processed %= real_average_distance
    result = []
    bridge_positions = []
    for line_index in positions:
        position = polygon.get_middle_of_line(line_index)
        # skip bridges that are close to another existing bridge
        if is_near_list(bridge_positions, position, avoid_distance):
            line = polygon.get_lines()[line_index]
            # calculate two alternative points on the same line
            position1 = pdiv(padd(position, line.p1), 2)
            position2 = pdiv(padd(position, line.p2), 2)
            if is_near_list(bridge_positions, position1, avoid_distance):
                if is_near_list(bridge_positions, position2, avoid_distance):
                    # no valid alternative - we skip this bridge
                    continue
                else:
                    # position2 is OK
                    position = position2
            else:
                # position1 is OK
                position = position1
        # append the original position (ignoring z_plane)
        bridge_positions.append(position)
        # move the point to z_plane
        position = (position[0], position[1], z_plane)
        bridge_dir = pnormalized(pcross(lines[line_index].dir,
                                        polygon.plane.n))
        result.append((position, bridge_dir))
    return result
Ejemplo n.º 10
0
 def get_shifted_vertex(self, index, offset):
     p1 = self._points[index]
     p2 = self._points[(index + 1) % len(self._points)]
     cross_offset = pnormalized(pcross(psub(p2, p1), self.plane.n))
     bisector_normalized = self.get_bisector(index)
     factor = pdot(cross_offset, bisector_normalized)
     if factor != 0:
         bisector_sized = pmul(bisector_normalized, offset / factor)
         return padd(p1, bisector_sized)
     else:
         return p2
Ejemplo n.º 11
0
def get_rotation_matrix_from_to(v_orig, v_dest):
    """ calculate the rotation matrix used to transform one vector into another

    The result is useful for modifying the rotation matrix of a 3d object.
    The simplest example is the following with the original vector pointing
    along the x axis, while the destination vectors goes along the y axis:
        get_rotation_matrix((1, 0, 0), (0, 1, 0))
    Basically this describes a rotation around the z axis by 90 degrees.
    The resulting 3x3 matrix (tuple of tuple of floats) can be multiplied with
    any other vector to rotate it in the same way around the z axis.
    @type v_orig: tuple(float) | list(float) | pycam.Geometry.Point
    @value v_orig: the original 3d vector
    @type v_dest: tuple(float) | list(float) | pycam.Geometry.Point
    @value v_dest: the destination 3d vector
    @rtype: tuple(tuple(float))
    @return: the rotation matrix (3x3)
    """

    v_orig_length = get_length(v_orig)
    v_dest_length = get_length(v_dest)
    cross_product = get_length(pcross(v_orig, v_dest))
    try:
        arcsin = cross_product / (v_orig_length * v_dest_length)
    except ZeroDivisionError:
        return None
    # prevent float inaccuracies to crash the calculation (within limits)
    if 1 < arcsin < 1 + epsilon:
        arcsin = 1.0
    elif -1 - epsilon < arcsin < -1:
        arcsin = -1.0
    rot_angle = math.asin(arcsin)
    # calculate the rotation axis
    # The rotation axis is equal to the cross product of the original and
    # destination vectors.
    rot_axis = pnormalized((v_orig[1] * v_dest[2] - v_orig[2] * v_dest[1],
                            v_orig[2] * v_dest[0] - v_orig[0] * v_dest[2],
                            v_orig[0] * v_dest[1] - v_orig[1] * v_dest[0]))
    if not rot_axis:
        return None
    # get the rotation matrix
    # see http://www.fastgraph.com/makegames/3drotation/
    c = math.cos(rot_angle)
    s = math.sin(rot_angle)
    t = 1 - c
    return ((t * rot_axis[0] * rot_axis[0] + c,
             t * rot_axis[0] * rot_axis[1] - s * rot_axis[2],
             t * rot_axis[0] * rot_axis[2] + s * rot_axis[1]),
            (t * rot_axis[0] * rot_axis[1] + s * rot_axis[2],
             t * rot_axis[1] * rot_axis[1] + c,
             t * rot_axis[1] * rot_axis[2] - s * rot_axis[0]),
            (t * rot_axis[0] * rot_axis[2] - s * rot_axis[1],
             t * rot_axis[1] * rot_axis[2] + s * rot_axis[0],
             t * rot_axis[2] * rot_axis[2] + c))
Ejemplo n.º 12
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)
Ejemplo n.º 13
0
def intersect_cylinder_line(center, axis, radius, radiussq, direction, edge):
    d = edge.dir
    # take a plane throught the line and along the cylinder axis (1)
    n = pcross(d, axis)
    if pnorm(n) == 0:
        # no contact point, but should check here if cylinder *always*
        # intersects line...
        return (None, None, INFINITE)
    n = pnormalized(n)
    # the contact line between the cylinder and this plane (1)
    # is where the surface normal is perpendicular to the plane
    # so line := ccl + \lambda * axis
    if pdot(n, direction) < 0:
        ccl = psub(center, pmul(n, radius))
    else:
        ccl = padd(center, pmul(n, radius))
    # now extrude the contact line along the direction, this is a plane (2)
    n2 = pcross(direction, axis)
    if pnorm(n2) == 0:
        # no contact point, but should check here if cylinder *always*
        # intersects line...
        return (None, None, INFINITE)
    n2 = pnormalized(n2)
    plane1 = Plane(ccl, n2)
    # intersect this plane with the line, this gives us the contact point
    (cp, l) = plane1.intersect_point(d, edge.p1)
    if not cp:
        return (None, None, INFINITE)
    # now take a plane through the contact line and perpendicular to the
    # direction (3)
    plane2 = Plane(ccl, direction)
    # the intersection of this plane (3) with the line through the contact point
    # gives us the cutter contact point
    (ccp, l) = plane2.intersect_point(direction, cp)
    cp = padd(ccp, pmul(direction, -l))
    return (ccp, cp, -l)
Ejemplo n.º 14
0
def intersect_cylinder_point(center, axis, radius, radiussq, direction, point):
    # take a plane along direction and axis
    n = pnormalized(pcross(direction, axis))
    # distance of the point to this plane
    d = pdot(n, point) - pdot(n, center)
    if abs(d) > radius - epsilon:
        return (None, None, INFINITE)
    # ccl is on cylinder
    d2 = sqrt(radiussq - d * d)
    ccl = padd(padd(center, pmul(n, d)), pmul(direction, d2))
    # take plane through ccl and axis
    plane = Plane(ccl, direction)
    # intersect point with plane
    (ccp, l) = plane.intersect_point(direction, point)
    return (ccp, point, -l)
Ejemplo n.º 15
0
 def intersect_triangle(self, triangle, counter_clockwise=False):
     """ Returns the line of intersection of a triangle with a plane.
     "None" is returned, if:
         - the triangle does not intersect with the plane
         - all vertices of the triangle are on the plane
     The line always runs clockwise through the triangle.
     """
     # don't import Line in the header -> circular import
     from pycam.Geometry.Line import Line
     collisions = []
     for edge, point in ((triangle.e1, triangle.p1),
                         (triangle.e2, triangle.p2), (triangle.e3,
                                                      triangle.p3)):
         cp, l = self.intersect_point(edge.dir, point)
         # filter all real collisions
         # We don't want to count vertices double -> thus we only accept
         # a distance that is lower than the length of the edge.
         if (cp is not None) and (-epsilon < l < edge.len - epsilon):
             collisions.append(cp)
         elif (cp is None) and (pdot(self.n, edge.dir) == 0):
             cp, dist = self.intersect_point(self.n, point)
             if abs(dist) < epsilon:
                 # the edge is on the plane
                 collisions.append(point)
     if len(collisions) == 3:
         # All points of the triangle are on the plane.
         # We don't return a waterline, as there should be another non-flat
         # triangle with the same waterline.
         return None
     if len(collisions) == 2:
         collision_line = Line(collisions[0], collisions[1])
         # no further calculation, if the line is zero-sized
         if collision_line.len == 0:
             return collision_line
         cross = pcross(self.n, collision_line.dir)
         if (pdot(cross, triangle.normal) <
                 0) == bool(not counter_clockwise):
             # anti-clockwise direction -> revert the direction of the line
             collision_line = Line(collision_line.p2, collision_line.p1)
         return collision_line
     elif len(collisions) == 1:
         # only one point is on the plane
         # This waterline (with zero length) should be of no use.
         return None
     else:
         return None
Ejemplo n.º 16
0
def _add_cuboid_to_model(model, start, direction, height, width):
    up = pmul((0, 0, 1, 'v'), height)
    ortho_dir = pnormalized(pcross(direction, up))
    start1 = padd(start, pmul(ortho_dir, -width / 2))
    start2 = padd(start1, up)
    start3 = padd(start2, pmul(ortho_dir, width))
    start4 = psub(start3, up)
    end1 = padd(start1, direction)
    end2 = padd(start2, direction)
    end3 = padd(start3, direction)
    end4 = padd(start4, direction)
    faces = ((start1, start2, start3, start4), (start1, end1, end2, start2),
             (start2, end2, end3, start3), (start3, end3, end4, start4),
             (start4, end4, end1, start1), (end4, end3, end2, end1))
    for face in faces:
        t1, t2 = _get_triangles_for_face(face)
        model.append(t1)
        model.append(t2)
Ejemplo n.º 17
0
def draw_direction_cone(p1, p2, position=0.5, precision=12, size=0.1):
    distance = psub(p2, p1)
    length = pnorm(distance)
    direction = pnormalized(distance)
    if direction is None:
        # zero-length line
        return
    cone_length = length * size
    cone_radius = cone_length / 3.0
    # move the cone to the middle of the line
    GL.glTranslatef((p1[0] + p2[0]) * position, (p1[1] + p2[1]) * position,
                    (p1[2] + p2[2]) * position)
    # rotate the cone according to the line direction
    # The cross product is a good rotation axis.
    cross = pcross(direction, (0, 0, -1))
    if pnorm(cross) != 0:
        # The line direction is not in line with the z axis.
        try:
            angle = math.asin(sqrt(direction[0]**2 + direction[1]**2))
        except ValueError:
            # invalid angle - just ignore this cone
            return
        # convert from radians to degree
        angle = angle / math.pi * 180
        if direction[2] < 0:
            angle = 180 - angle
        GL.glRotatef(angle, cross[0], cross[1], cross[2])
    elif direction[2] == -1:
        # The line goes down the z axis - turn it around.
        GL.glRotatef(180, 1, 0, 0)
    else:
        # The line goes up the z axis - nothing to be done.
        pass
    # center the cone
    GL.glTranslatef(0, 0, -cone_length * position)
    # draw the cone
    GLUT.glutSolidCone(cone_radius, cone_length, precision, 1)
Ejemplo n.º 18
0
 def draw_direction_cone_mesh(self,
                              p1,
                              p2,
                              position=0.5,
                              precision=12,
                              size=0.1):
     distance = psub(p2, p1)
     length = pnorm(distance)
     direction = pnormalized(distance)
     if direction is None or length < 0.5:
         # zero-length line
         return []
     cone_length = length * size
     cone_radius = cone_length / 3.0
     bottom = padd(p1, pmul(psub(p2, p1), position - size / 2))
     top = padd(p1, pmul(psub(p2, p1), position + size / 2))
     # generate a a line perpendicular to this line, cross product is good at this
     cross = pcross(direction, (0, 0, -1))
     conepoints = []
     if pnorm(cross) != 0:
         # The line direction is not in line with the z axis.
         conep1 = padd(bottom, pmul(cross, cone_radius))
         conepoints = [
             self._rotate_point(conep1, bottom, direction, x)
             for x in numpy.linspace(0, 2 * math.pi, precision)
         ]
     else:
         # Z axis
         # just add cone radius to the x axis and rotate the point
         conep1 = (bottom[0] + cone_radius, bottom[1], bottom[2])
         conepoints = [
             self._rotate_point(conep1, p1, direction, x)
             for x in numpy.linspace(0, 2 * math.pi, precision)
         ]
     triangles = [(top, conepoints[idx], conepoints[idx + 1])
                  for idx in range(len(conepoints) - 1)]
     return triangles
Ejemplo n.º 19
0
def intersect_circle_line(center, axis, radius, radiussq, direction, edge):
    # make a plane by sliding the line along the direction (1)
    d = edge.dir
    if pdot(d, axis) == 0:
        if pdot(direction, axis) == 0:
            return (None, None, INFINITE)
        plane = Plane(center, axis)
        (p1, l) = plane.intersect_point(direction, edge.p1)
        (p2, l) = plane.intersect_point(direction, edge.p2)
        pc = Line(p1, p2).closest_point(center)
        d_sq = pnormsq(psub(pc, center))
        if d_sq >= radiussq:
            return (None, None, INFINITE)
        a = sqrt(radiussq - d_sq)
        d1 = pdot(psub(p1, pc), d)
        d2 = pdot(psub(p2, pc), d)
        ccp = None
        cp = None
        if abs(d1) < a - epsilon:
            ccp = p1
            cp = psub(p1, pmul(direction, l))
        elif abs(d2) < a - epsilon:
            ccp = p2
            cp = psub(p2, pmul(direction, l))
        elif ((d1 < -a + epsilon) and (d2 > a - epsilon)) \
                or ((d2 < -a + epsilon) and (d1 > a - epsilon)):
            ccp = pc
            cp = psub(pc, pmul(direction, l))
        return (ccp, cp, -l)
    n = pcross(d, direction)
    if pnorm(n) == 0:
        # no contact point, but should check here if circle *always* intersects
        # line...
        return (None, None, INFINITE)
    n = pnormalized(n)
    # take a plane through the base
    plane = Plane(center, axis)
    # intersect base with line
    (lp, l) = plane.intersect_point(d, edge.p1)
    if not lp:
        return (None, None, INFINITE)
    # intersection of 2 planes: lp + \lambda v
    v = pcross(axis, n)
    if pnorm(v) == 0:
        return (None, None, INFINITE)
    v = pnormalized(v)
    # take plane through intersection line and parallel to axis
    n2 = pcross(v, axis)
    if pnorm(n2) == 0:
        return (None, None, INFINITE)
    n2 = pnormalized(n2)
    # distance from center to this plane
    dist = pdot(n2, center) - pdot(n2, lp)
    distsq = dist * dist
    if distsq > radiussq - epsilon:
        return (None, None, INFINITE)
    # must be on circle
    dist2 = sqrt(radiussq - distsq)
    if pdot(d, axis) < 0:
        dist2 = -dist2
    ccp = psub(center, psub(pmul(n2, dist), pmul(v, dist2)))
    plane = Plane(edge.p1, pcross(pcross(d, direction), d))
    (cp, l) = plane.intersect_point(direction, ccp)
    return (ccp, cp, l)
Ejemplo n.º 20
0
 def to_opengl(self, color=None, show_directions=False):
     if not GL_enabled:
         return
     if color is not None:
         GL.glColor4f(*color)
     GL.glBegin(GL.GL_TRIANGLES)
     # use normals to improve lighting (contributed by imyrek)
     normal_t = self.normal
     GL.glNormal3f(normal_t[0], normal_t[1], normal_t[2])
     # The triangle's points are in clockwise order, but GL expects
     # counter-clockwise sorting.
     GL.glVertex3f(self.p1[0], self.p1[1], self.p1[2])
     GL.glVertex3f(self.p3[0], self.p3[1], self.p3[2])
     GL.glVertex3f(self.p2[0], self.p2[1], self.p2[2])
     GL.glEnd()
     if show_directions:
         # display surface normals
         n = self.normal
         c = self.center
         d = 0.5
         GL.glBegin(GL.GL_LINES)
         GL.glVertex3f(c[0], c[1], c[2])
         GL.glVertex3f(c[0] + n[0] * d, c[1] + n[1] * d, c[2] + n[2] * d)
         GL.glEnd()
     if False:
         # display bounding sphere
         GL.glPushMatrix()
         middle = self.middle
         GL.glTranslate(middle[0], middle[1], middle[2])
         if not hasattr(self, "_sphere"):
             self._sphere = GLU.gluNewQuadric()
         GLU.gluSphere(self._sphere, self.radius, 10, 10)
         GL.glPopMatrix()
     if pycam.Utils.log.is_debug():
         # draw triangle id on triangle face
         GL.glPushMatrix()
         c = self.center
         GL.glTranslate(c[0], c[1], c[2])
         p12 = pmul(padd(self.p1, self.p2), 0.5)
         p3_12 = pnormalized(psub(self.p3, p12))
         p2_1 = pnormalized(psub(self.p1, self.p2))
         pn = pcross(p2_1, p3_12)
         GL.glMultMatrixf((p2_1[0], p2_1[1], p2_1[2], 0, p3_12[0], p3_12[1],
                           p3_12[2], 0, pn[0], pn[1], pn[2], 0, 0, 0, 0, 1))
         n = pmul(self.normal, 0.01)
         GL.glTranslatef(n[0], n[1], n[2])
         maxdim = max((self.maxx - self.minx), (self.maxy - self.miny),
                      (self.maxz - self.minz))
         factor = 0.001
         GL.glScalef(factor * maxdim, factor * maxdim, factor * maxdim)
         w = 0
         id_string = "%s." % str(self.id)
         for ch in id_string:
             w += GLUT.glutStrokeWidth(GLUT_STROKE_ROMAN, ord(ch))
         GL.glTranslate(-w / 2, 0, 0)
         for ch in id_string:
             GLUT.glutStrokeCharacter(GLUT_STROKE_ROMAN, ord(ch))
         GL.glPopMatrix()
     if False:
         # draw point id on triangle face
         c = self.center
         p12 = pmul(padd(self.p1, self.p2), 0.5)
         p3_12 = pnormalized(psub(self.p3, p12))
         p2_1 = pnormalized(psub(self.p1, self.p2))
         pn = pcross(p2_1, p3_12)
         n = pmul(self.normal, 0.01)
         for p in (self.p1, self.p2, self.p3):
             GL.glPushMatrix()
             pp = psub(p, pmul(psub(p, c), 0.3))
             GL.glTranslate(pp[0], pp[1], pp[2])
             GL.glMultMatrixf(
                 (p2_1[0], p2_1[1], p2_1[2], 0, p3_12[0], p3_12[1],
                  p3_12[2], 0, pn[0], pn[1], pn[2], 0, 0, 0, 0, 1))
             GL.glTranslatef(n[0], n[1], n[2])
             GL.glScalef(0.001, 0.001, 0.001)
             w = 0
             for ch in str(p.id):
                 w += GLUT.glutStrokeWidth(GLUT_STROKE_ROMAN, ord(ch))
                 GL.glTranslate(-w / 2, 0, 0)
             for ch in str(p.id):
                 GLUT.glutStrokeCharacter(GLUT_STROKE_ROMAN, ord(ch))
             GL.glPopMatrix()
Ejemplo n.º 21
0
def get_spiral_layer(minx, maxx, miny, maxy, z, line_distance, step_width,
                     grid_direction, start_position, rounded_corners, reverse):
    current_location = _get_position(minx, maxx, miny, maxy, z, start_position)
    if line_distance > 0:
        line_steps_x = math.ceil((float(maxx - minx) / line_distance))
        line_steps_y = math.ceil((float(maxy - miny) / line_distance))
        line_distance_x = (maxx - minx) / line_steps_x
        line_distance_y = (maxy - miny) / line_steps_y
        lines = get_spiral_layer_lines(minx, maxx, miny, maxy, z,
                                       line_distance_x, line_distance_y,
                                       grid_direction, start_position,
                                       current_location)
        if reverse:
            lines.reverse()
        # turn the lines into steps
        if rounded_corners:
            rounded_lines = []
            previous = None
            for index, (start, end) in enumerate(lines):
                radius = 0.5 * min(line_distance_x, line_distance_y)
                edge_vector = psub(end, start)
                # TODO: ellipse would be better than arc
                offset = pmul(pnormalized(edge_vector), radius)
                if previous:
                    start = padd(start, offset)
                    center = padd(previous, offset)
                    up_vector = pnormalized(
                        pcross(psub(previous, center), psub(start, center)))
                    north = padd(center, (1.0, 0.0, 0.0, 'v'))
                    angle_start = get_angle_pi(
                        north, center, previous, up_vector,
                        pi_factor=True) * 180.0
                    angle_end = get_angle_pi(
                        north, center, start, up_vector,
                        pi_factor=True) * 180.0
                    # TODO: remove these exceptions based on up_vector.z (get_points_of_arc does
                    #       not respect the plane, yet)
                    if up_vector[2] < 0:
                        angle_start, angle_end = -angle_end, -angle_start
                    arc_points = get_points_of_arc(center, radius, angle_start,
                                                   angle_end)
                    if up_vector[2] < 0:
                        arc_points.reverse()
                    for arc_index in range(len(arc_points) - 1):
                        p1_coord = arc_points[arc_index]
                        p2_coord = arc_points[arc_index + 1]
                        p1 = (p1_coord[0], p1_coord[1], z)
                        p2 = (p2_coord[0], p2_coord[1], z)
                        rounded_lines.append((p1, p2))
                if index != len(lines) - 1:
                    end = psub(end, offset)
                previous = end
                rounded_lines.append((start, end))
            lines = rounded_lines
        for start, end in lines:
            points = []
            if step_width is None:
                points.append(start)
                points.append(end)
            else:
                line = Line(start, end)
                if isiterable(step_width):
                    steps = step_width
                else:
                    steps = floatrange(0.0, line.len, inc=step_width)
                for step in steps:
                    next_point = padd(line.p1, pmul(line.dir, step))
                    points.append(next_point)
            if reverse:
                points.reverse()
            yield points
Ejemplo n.º 22
0
 def get_area(self):
     cross = pcross(psub(self.p2, self.p1), psub(self.p3, self.p1))
     return pnorm(cross) / 2
Ejemplo n.º 23
0
def ImportModel(filename, use_kdtree=True, callback=None, **kwargs):
    global vertices, edges, kdtree
    vertices = 0
    edges = 0
    kdtree = None

    normal_conflict_warning_seen = False

    if hasattr(filename, "read"):
        # make sure that the input stream can seek and has ".len"
        f = StringIO(filename.read())
        # useful for later error messages
        filename = "input stream"
    else:
        try:
            url_file = pycam.Utils.URIHandler(filename).open()
            # urllib.urlopen objects do not support "seek" - so we need to read
            # the whole file at once. This is ugly - anyone with a better idea?
            f = StringIO(url_file.read())
            # TODO: the above ".read" may be incomplete - this is ugly
            # see http://patrakov.blogspot.com/2011/03/case-of-non-raised-exception.html
            # and http://stackoverflow.com/questions/1824069/
            url_file.close()
        except IOError as err_msg:
            log.error("STLImporter: Failed to read file (%s): %s", filename,
                      err_msg)
            return None
    # Read the first two lines of (potentially non-binary) input - they should
    # contain "solid" and "facet".
    header_lines = []
    while len(header_lines) < 2:
        line = f.readline(200)
        if len(line) == 0:
            # empty line (not even a line-feed) -> EOF
            log.error("STLImporter: No valid lines found in '%s'", filename)
            return None
        # ignore comment lines
        # note: partial comments (starting within a line) are not handled
        if not line.startswith(";"):
            header_lines.append(line)
    header = "".join(header_lines)
    # read byte 80 to 83 - they contain the "numfacets" value in binary format
    f.seek(80)
    numfacets = unpack("<I", f.read(4))[0]
    binary = False
    log.debug("STL import info: %s / %s / %s / %s", f.len, numfacets,
              header.find("solid"), header.find("facet"))

    if f.len == (84 + 50 * numfacets):
        binary = True
    elif header.find("solid") >= 0 and header.find("facet") >= 0:
        binary = False
        f.seek(0)
    else:
        log.error("STLImporter: STL binary/ascii detection failed")
        return None

    if use_kdtree:
        kdtree = PointKdtree([], 3, 1, epsilon)
    model = Model(use_kdtree)

    t = None
    p1 = None
    p2 = None
    p3 = None

    if binary:
        for i in range(1, numfacets + 1):
            if callback and callback():
                log.warn("STLImporter: load model operation cancelled")
                return None
            a1 = unpack("<f", f.read(4))[0]
            a2 = unpack("<f", f.read(4))[0]
            a3 = unpack("<f", f.read(4))[0]

            n = (float(a1), float(a2), float(a3), 'v')

            v11 = unpack("<f", f.read(4))[0]
            v12 = unpack("<f", f.read(4))[0]
            v13 = unpack("<f", f.read(4))[0]

            p1 = UniqueVertex(float(v11), float(v12), float(v13))

            v21 = unpack("<f", f.read(4))[0]
            v22 = unpack("<f", f.read(4))[0]
            v23 = unpack("<f", f.read(4))[0]

            p2 = UniqueVertex(float(v21), float(v22), float(v23))

            v31 = unpack("<f", f.read(4))[0]
            v32 = unpack("<f", f.read(4))[0]
            v33 = unpack("<f", f.read(4))[0]

            p3 = UniqueVertex(float(v31), float(v32), float(v33))

            # not used (additional attributes)
            f.read(2)

            dotcross = pdot(n, pcross(psub(p2, p1), psub(p3, p1)))
            if a1 == a2 == a3 == 0:
                dotcross = pcross(psub(p2, p1), psub(p3, p1))[2]
                n = None

            if dotcross > 0:
                # Triangle expects the vertices in clockwise order
                t = Triangle(p1, p3, p2)
            elif dotcross < 0:
                if not normal_conflict_warning_seen:
                    log.warn(
                        "Inconsistent normal/vertices found in facet definition %d of '%s'. "
                        "Please validate the STL file!", i, filename)
                    normal_conflict_warning_seen = True
                t = Triangle(p1, p2, p3)
            else:
                # the three points are in a line - or two points are identical
                # usually this is caused by points, that are too close together
                # check the tolerance value in pycam/Geometry/PointKdtree.py
                log.warn(
                    "Skipping invalid triangle: %s / %s / %s (maybe the resolution of the "
                    "model is too high?)", p1, p2, p3)
                continue
            if n:
                t.normal = n

            model.append(t)
    else:
        solid = re.compile(r"\s*solid\s+(\w+)\s+.*")
        endsolid = re.compile(r"\s*endsolid\s*")
        facet = re.compile(r"\s*facet\s*")
        normal = re.compile(
            r"\s*facet\s+normal" +
            r"\s+(?P<x>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" +
            r"\s+(?P<y>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" +
            r"\s+(?P<z>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)\s+")
        endfacet = re.compile(r"\s*endfacet\s+")
        loop = re.compile(r"\s*outer\s+loop\s+")
        endloop = re.compile(r"\s*endloop\s+")
        vertex = re.compile(
            r"\s*vertex" +
            r"\s+(?P<x>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" +
            r"\s+(?P<y>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" +
            r"\s+(?P<z>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)\s+")

        current_line = 0

        for line in f:
            if callback and callback():
                log.warn("STLImporter: load model operation cancelled")
                return None
            current_line += 1
            m = solid.match(line)
            if m:
                model.name = m.group(1)
                continue

            m = facet.match(line)
            if m:
                m = normal.match(line)
                if m:
                    n = (float(m.group('x')), float(m.group('y')),
                         float(m.group('z')), 'v')
                else:
                    n = None
                continue
            m = loop.match(line)
            if m:
                continue
            m = vertex.match(line)
            if m:
                p = UniqueVertex(float(m.group('x')), float(m.group('y')),
                                 float(m.group('z')))
                if p1 is None:
                    p1 = p
                elif p2 is None:
                    p2 = p
                elif p3 is None:
                    p3 = p
                else:
                    log.error(
                        "STLImporter: more then 3 points in facet (line %d)",
                        current_line)
                continue
            m = endloop.match(line)
            if m:
                continue
            m = endfacet.match(line)
            if m:
                if None in (p1, p2, p3):
                    log.warn(
                        "Invalid facet definition in line %d of '%s'. Please validate the "
                        "STL file!", current_line, filename)
                    n, p1, p2, p3 = None, None, None, None
                    continue
                if not n:
                    n = pnormalized(pcross(psub(p2, p1), psub(p3, p1)))

                # validate the normal
                # The three vertices of a triangle in an STL file are supposed
                # to be in counter-clockwise order. This should match the
                # direction of the normal.
                if n is None:
                    # invalid triangle (zero-length vector)
                    dotcross = 0
                else:
                    # make sure the points are in ClockWise order
                    dotcross = pdot(n, pcross(psub(p2, p1), psub(p3, p1)))
                if dotcross > 0:
                    # Triangle expects the vertices in clockwise order
                    t = Triangle(p1, p3, p2, n)
                elif dotcross < 0:
                    if not normal_conflict_warning_seen:
                        log.warn(
                            "Inconsistent normal/vertices found in line %d of '%s'. Please "
                            "validate the STL file!", current_line, filename)
                        normal_conflict_warning_seen = True
                    t = Triangle(p1, p2, p3, n)
                else:
                    # The three points are in a line - or two points are
                    # identical. Usually this is caused by points, that are too
                    # close together. Check the tolerance value in
                    # pycam/Geometry/PointKdtree.py.
                    log.warn(
                        "Skipping invalid triangle: %s / %s / %s (maybe the resolution of "
                        "the model is too high?)", p1, p2, p3)
                    n, p1, p2, p3 = (None, None, None, None)
                    continue
                n, p1, p2, p3 = (None, None, None, None)
                model.append(t)
                continue
            m = endsolid.match(line)
            if m:
                continue

    log.info("Imported STL model: %d vertices, %d edges, %d triangles",
             vertices, edges, len(model.triangles()))
    vertices = 0
    edges = 0
    kdtree = None

    if not model:
        # no valid items added to the model
        return None
    else:
        return model
Ejemplo n.º 24
0
def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z):
    # TODO: there are problems with "material allowance > 0"
    plane = Plane((0, 0, z), up_vector)
    if triangle.minz >= z:
        # no point of the triangle is below z
        # try all edges
        # Case (4)
        proj_points = []
        for p in triangle.get_points():
            proj_p = plane.get_point_projection(p)
            if proj_p not in proj_points:
                proj_points.append(proj_p)
        if len(proj_points) == 3:
            edges = []
            for index in range(3):
                edge = Line(proj_points[index - 1], proj_points[index])
                # the edge should be clockwise around the model
                if pdot(pcross(edge.dir, triangle.normal), up_vector) < 0:
                    edge = Line(edge.p2, edge.p1)
                edges.append((edge, proj_points[index - 2]))
            outer_edges = []
            for edge, other_point in edges:
                # pick only edges, where the other point is on the right side
                if pdot(pcross(psub(other_point, edge.p1), edge.dir),
                        up_vector) > 0:
                    outer_edges.append(edge)
            if len(outer_edges) == 0:
                # the points seem to be an one line
                # pick the longest edge
                long_edge = edges[0][0]
                for edge, other_point in edges[1:]:
                    if edge.len > long_edge.len:
                        long_edge = edge
                outer_edges = [long_edge]
        else:
            edge = Line(proj_points[0], proj_points[1])
            if pdot(pcross(edge.dir, triangle.normal), up_vector) < 0:
                edge = Line(edge.p2, edge.p1)
            outer_edges = [edge]
    else:
        # some parts of the triangle are above and some below the cutter level
        # Cases (2a), (2b), (3a) and (3b)
        points_above = [
            plane.get_point_projection(p) for p in triangle.get_points()
            if p[2] > z
        ]
        waterline = plane.intersect_triangle(triangle)
        if waterline is None:
            if len(points_above) == 0:
                # the highest point of the triangle is at z
                outer_edges = []
            else:
                if abs(triangle.minz - z) < epsilon:
                    # This is just an accuracy issue (see the
                    # "triangle.minz >= z" statement above).
                    outer_edges = []
                elif not [
                        p for p in triangle.get_points() if p[2] > z + epsilon
                ]:
                    # same as above: fix for inaccurate floating calculations
                    outer_edges = []
                else:
                    # this should not happen
                    raise ValueError((
                        "Could not find a waterline, but there are points above z "
                        "level (%f): %s / %s") % (z, triangle, points_above))
        else:
            # remove points that are not part of the waterline
            points_above = [
                p for p in points_above
                if (p != waterline.p1) and (p != waterline.p2)
            ]
            if len(points_above) == 0:
                # part of case (2a)
                outer_edges = [waterline]
            elif len(points_above) == 1:
                other_point = points_above[0]
                dot = pdot(
                    pcross(psub(other_point, waterline.p1), waterline.dir),
                    up_vector)
                if dot > 0:
                    # Case (2b)
                    outer_edges = [waterline]
                elif dot < 0:
                    # Case (3b)
                    edges = []
                    edges.append(Line(waterline.p1, other_point))
                    edges.append(Line(waterline.p2, other_point))
                    outer_edges = []
                    for edge in edges:
                        if pdot(pcross(edge.dir, triangle.normal),
                                up_vector) < 0:
                            outer_edges.append(Line(edge.p2, edge.p1))
                        else:
                            outer_edges.append(edge)
                else:
                    # the three points are on one line
                    # part of case (2a)
                    edges = []
                    edges.append(waterline)
                    edges.append(Line(waterline.p1, other_point))
                    edges.append(Line(waterline.p2, other_point))
                    edges.sort(key=lambda x: x.len)
                    edge = edges[-1]
                    if pdot(pcross(edge.dir, triangle.normal), up_vector) < 0:
                        outer_edges = [Line(edge.p2, edge.p1)]
                    else:
                        outer_edges = [edge]
            else:
                # two points above
                other_point = points_above[0]
                dot = pdot(
                    pcross(psub(other_point, waterline.p1), waterline.dir),
                    up_vector)
                if dot > 0:
                    # Case (2b)
                    # the other two points are on the right side
                    outer_edges = [waterline]
                elif dot < 0:
                    # Case (3a)
                    edge = Line(points_above[0], points_above[1])
                    if pdot(pcross(edge.dir, triangle.normal), up_vector) < 0:
                        outer_edges = [Line(edge.p2, edge.p1)]
                    else:
                        outer_edges = [edge]
                else:
                    edges = []
                    # pick the longest combination of two of these points
                    # part of case (2a)
                    # TODO: maybe we should use the waterline instead?
                    # (otherweise the line could be too long and thus
                    # connections to the adjacent waterlines are not discovered?
                    # Test this with an appropriate test model.)
                    points = [waterline.p1, waterline.p2] + points_above
                    for p1 in points:
                        for p2 in points:
                            if p1 is not p2:
                                edges.append(Line(p1, p2))
                    edges.sort(key=lambda x: x.len)
                    edge = edges[-1]
                    if pdot(pcross(edge.dir, triangle.normal), up_vector) < 0:
                        outer_edges = [Line(edge.p2, edge.p1)]
                    else:
                        outer_edges = [edge]
    # calculate the maximum diagonal length within the model
    x_dim = abs(model.maxx - model.minx)
    y_dim = abs(model.maxy - model.miny)
    z_dim = abs(model.maxz - model.minz)
    max_length = sqrt(x_dim**2 + y_dim**2 + z_dim**2)
    result = []
    for edge in outer_edges:
        direction = pnormalized(pcross(up_vector, edge.dir))
        if direction is None:
            continue
        direction = pmul(direction, max_length)
        edge_dir = psub(edge.p2, edge.p1)
        # TODO: Adapt the number of potential starting positions to the length
        # of the line. Don't use 0.0 and 1.0 - this could result in ambiguous
        # collisions with triangles sharing these vertices.
        for factor in (0.5, epsilon, 1.0 - epsilon, 0.25, 0.75):
            start = padd(edge.p1, pmul(edge_dir, factor))
            # We need to use the triangle collision algorithm here - because we
            # need the point of collision in the triangle.
            collisions = get_free_paths_triangles([model],
                                                  cutter,
                                                  start,
                                                  padd(start, direction),
                                                  return_triangles=True)
            for index, coll in enumerate(collisions):
                if ((index % 2 == 0) and (coll[1] is not None)
                        and (coll[2] is not None)
                        and (pdot(psub(coll[0], start), direction) > 0)):
                    cl, hit_t, cp = coll
                    break
            else:
                log.debug("Failed to detect any collision: %s / %s -> %s",
                          edge, start, direction)
                continue
            proj_cp = plane.get_point_projection(cp)
            # e.g. the Spherical Cutter often does not collide exactly above
            # the potential collision line.
            # TODO: maybe an "is cp inside of the triangle" check would be good?
            if (triangle is hit_t) or (edge.is_point_inside(proj_cp)):
                result.append((cl, edge))
                # continue with the next outer_edge
                break
    # Don't check triangles again that are completely above the z level and
    # did not return any collisions.
    if not result and (triangle.minz > z):
        # None indicates that the triangle needs no further evaluation
        return None
    return result
Ejemplo n.º 25
0
def import_model(filename, use_kdtree=True, callback=None, **kwargs):
    global vertices, edges, kdtree
    vertices = 0
    edges = 0
    kdtree = None

    normal_conflict_warning_seen = False

    if hasattr(filename, "read"):
        # make sure that the input stream can seek and has ".len"
        f = BufferedReader(filename)
        # useful for later error messages
        filename = "input stream"
    else:
        try:
            url_file = pycam.Utils.URIHandler(filename).open()
            # urllib.urlopen objects do not support "seek" - so we need a buffered reader
            # Is there a better approach than consuming the whole file at once?
            f = BufferedReader(BytesIO(url_file.read()))
            url_file.close()
        except IOError as exc:
            raise LoadFileError(
                "STLImporter: Failed to read file ({}): {}".format(
                    filename, exc))

    # the facet count is only available for the binary format
    facet_count = get_facet_count_if_binary_format(f)
    is_binary = (facet_count is not None)

    if use_kdtree:
        kdtree = PointKdtree([], 3, 1, epsilon)
    model = Model(use_kdtree)

    t = None
    p1 = None
    p2 = None
    p3 = None

    if is_binary:
        # Skip the header and count fields of binary stl file
        f.seek(HEADER_SIZE + COUNT_SIZE)

        for i in range(1, facet_count + 1):
            if callback and callback():
                raise AbortOperationException(
                    "STLImporter: load model operation cancelled")
            a1 = unpack("<f", f.read(4))[0]
            a2 = unpack("<f", f.read(4))[0]
            a3 = unpack("<f", f.read(4))[0]

            n = (float(a1), float(a2), float(a3), 'v')

            v11 = unpack("<f", f.read(4))[0]
            v12 = unpack("<f", f.read(4))[0]
            v13 = unpack("<f", f.read(4))[0]

            p1 = get_unique_vertex(float(v11), float(v12), float(v13))

            v21 = unpack("<f", f.read(4))[0]
            v22 = unpack("<f", f.read(4))[0]
            v23 = unpack("<f", f.read(4))[0]

            p2 = get_unique_vertex(float(v21), float(v22), float(v23))

            v31 = unpack("<f", f.read(4))[0]
            v32 = unpack("<f", f.read(4))[0]
            v33 = unpack("<f", f.read(4))[0]

            p3 = get_unique_vertex(float(v31), float(v32), float(v33))

            # not used (additional attributes)
            f.read(2)

            dotcross = pdot(n, pcross(psub(p2, p1), psub(p3, p1)))
            if a1 == a2 == a3 == 0:
                dotcross = pcross(psub(p2, p1), psub(p3, p1))[2]
                n = None

            if dotcross > 0:
                # Triangle expects the vertices in clockwise order
                t = Triangle(p1, p3, p2)
            elif dotcross < 0:
                if not normal_conflict_warning_seen:
                    log.warn(
                        "Inconsistent normal/vertices found in facet definition %d of '%s'. "
                        "Please validate the STL file!", i, filename)
                    normal_conflict_warning_seen = True
                t = Triangle(p1, p2, p3)
            else:
                # the three points are in a line - or two points are identical
                # usually this is caused by points, that are too close together
                # check the tolerance value in pycam/Geometry/PointKdtree.py
                log.warn(
                    "Skipping invalid triangle: %s / %s / %s (maybe the resolution of the "
                    "model is too high?)", p1, p2, p3)
                continue
            if n:
                t.normal = n

            model.append(t)
    else:
        # from here on we want to use a text based input stream (not bytes)
        f = TextIOWrapper(f, encoding="utf-8")
        solid = re.compile(r"\s*solid\s+(\w+)\s+.*")
        endsolid = re.compile(r"\s*endsolid\s*")
        facet = re.compile(r"\s*facet\s*")
        normal = re.compile(
            r"\s*facet\s+normal" +
            r"\s+(?P<x>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" +
            r"\s+(?P<y>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" +
            r"\s+(?P<z>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)\s+")
        endfacet = re.compile(r"\s*endfacet\s+")
        loop = re.compile(r"\s*outer\s+loop\s+")
        endloop = re.compile(r"\s*endloop\s+")
        vertex = re.compile(
            r"\s*vertex" +
            r"\s+(?P<x>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" +
            r"\s+(?P<y>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" +
            r"\s+(?P<z>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)\s+")

        current_line = 0

        for line in f:
            if callback and callback():
                raise AbortOperationException(
                    "STLImporter: load model operation cancelled")
            current_line += 1
            m = solid.match(line)
            if m:
                model.name = m.group(1)
                continue

            m = facet.match(line)
            if m:
                m = normal.match(line)
                if m:
                    n = (float(m.group('x')), float(m.group('y')),
                         float(m.group('z')), 'v')
                else:
                    n = None
                continue
            m = loop.match(line)
            if m:
                continue
            m = vertex.match(line)
            if m:
                p = get_unique_vertex(float(m.group('x')), float(m.group('y')),
                                      float(m.group('z')))
                if p1 is None:
                    p1 = p
                elif p2 is None:
                    p2 = p
                elif p3 is None:
                    p3 = p
                else:
                    log.error(
                        "STLImporter: more then 3 points in facet (line %d)",
                        current_line)
                continue
            m = endloop.match(line)
            if m:
                continue
            m = endfacet.match(line)
            if m:
                if None in (p1, p2, p3):
                    log.warn(
                        "Invalid facet definition in line %d of '%s'. Please validate the "
                        "STL file!", current_line, filename)
                    n, p1, p2, p3 = None, None, None, None
                    continue
                if not n:
                    n = pnormalized(pcross(psub(p2, p1), psub(p3, p1)))

                # validate the normal
                # The three vertices of a triangle in an STL file are supposed
                # to be in counter-clockwise order. This should match the
                # direction of the normal.
                if n is None:
                    # invalid triangle (zero-length vector)
                    dotcross = 0
                else:
                    # make sure the points are in ClockWise order
                    dotcross = pdot(n, pcross(psub(p2, p1), psub(p3, p1)))
                if dotcross > 0:
                    # Triangle expects the vertices in clockwise order
                    t = Triangle(p1, p3, p2, n)
                elif dotcross < 0:
                    if not normal_conflict_warning_seen:
                        log.warn(
                            "Inconsistent normal/vertices found in line %d of '%s'. Please "
                            "validate the STL file!", current_line, filename)
                        normal_conflict_warning_seen = True
                    t = Triangle(p1, p2, p3, n)
                else:
                    # The three points are in a line - or two points are
                    # identical. Usually this is caused by points, that are too
                    # close together. Check the tolerance value in
                    # pycam/Geometry/PointKdtree.py.
                    log.warn(
                        "Skipping invalid triangle: %s / %s / %s (maybe the resolution of "
                        "the model is too high?)", p1, p2, p3)
                    n, p1, p2, p3 = (None, None, None, None)
                    continue
                n, p1, p2, p3 = (None, None, None, None)
                model.append(t)
                continue
            m = endsolid.match(line)
            if m:
                continue

    # TODO display unique vertices and edges count - currently not counted
    log.info("Imported STL model: %d triangles", len(model.triangles()))
    vertices = 0
    edges = 0
    kdtree = None

    if not model:
        # no valid items added to the model
        raise LoadFileError(
            "Failed to load model from STL file: no elements found")
    else:
        return model