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)
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)
def get_lines_layer(lines, z, last_z=None, step_width=None, milling_style=MillingStyle.CONVENTIONAL): get_proj_point = lambda proj_point: (proj_point[0], proj_point[1], z) projected_lines = [] for line in lines: if (last_z is not None) and (last_z < line.minz): # the line was processed before continue elif line.minz < z < line.maxz: # Split the line at the point at z level and do the calculation # for both point pairs. factor = (z - line.p1[2]) / (line.p2[2] - line.p1[2]) plane_point = padd(line.p1, pmul(line.vector, factor)) if line.p1[2] < z: p1 = get_proj_point(line.p1) p2 = line.p2 else: p1 = line.p1 p2 = get_proj_point(line.p2) projected_lines.append(Line(p1, plane_point)) yield Line(plane_point, p2) elif line.minz < last_z < line.maxz: plane = Plane((0, 0, last_z), (0, 0, 1, 'v')) cp = plane.intersect_point(line.dir, line.p1)[0] # we can be sure that there is an intersection if line.p1[2] > last_z: p1, p2 = cp, line.p2 else: p1, p2 = line.p1, cp projected_lines.append(Line(p1, p2)) else: if line.maxz <= z: # the line is completely below z projected_lines.append( Line(get_proj_point(line.p1), get_proj_point(line.p2))) elif line.minz >= z: projected_lines.append(line) else: _log.warn( "Unexpected condition 'get_lines_layer': %s / %s / %s / %s", line.p1, line.p2, z, last_z) # process all projected lines for line in projected_lines: points = [] if step_width is None: points.append(line.p1) points.append(line.p2) else: 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) yield points
def intersect_sphere_plane(center, radius, direction, triangle): # let n be the normal to the plane n = triangle.normal if pdot(n, direction) == 0: return (None, None, INFINITE) # the cutter contact point is on the sphere, where the surface normal is n if pdot(n, direction) < 0: ccp = psub(center, pmul(n, radius)) else: ccp = padd(center, pmul(n, radius)) # intersect the plane with a line through the contact point (cp, d) = triangle.plane.intersect_point(direction, ccp) return (ccp, cp, d)
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)
def filter_toolpath(self, toolpath): feedrate = min_feedrate = 1 new_path = [] last_pos = None limit = self.settings["timelimit"] duration = 0 for move_type, args in toolpath: if move_type in (MOVE_STRAIGHT, MOVE_STRAIGHT_RAPID): if last_pos: new_distance = pdist(args, last_pos) new_duration = new_distance / max(feedrate, min_feedrate) if (new_duration > 0) and (duration + new_duration > limit): partial = (limit - duration) / new_duration destination = padd(last_pos, pmul(psub(args, last_pos), partial)) duration = limit else: destination = args duration += new_duration else: destination = args new_path.append((move_type, destination)) last_pos = args if (move_type == MACHINE_SETTING) and (args[0] == "feedrate"): feedrate = args[1] if duration >= limit: break return new_path
def calc_normal(main, normals): suitable = (0, 0, 0, 'v') for normal, weight in normals: dot = pdot(main, normal) if dot > 0: suitable = padd(suitable, pmul(normal, weight * dot)) return pnormalized(suitable)
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>"
def closest_point(self, p): v = self.dir if v is None: # for zero-length lines return self.p1 dist = pdot(self.p1, v) - pdot(p, v) return psub(self.p1, pmul(v, dist))
def filter_toolpath(self, toolpath): feedrate = min_feedrate = 1 new_path = [] last_pos = None limit = self.settings["timelimit"] duration = 0 for step in toolpath: if step.action in MOVES_LIST: if last_pos: new_distance = pdist(step.position, last_pos) new_duration = new_distance / max(feedrate, min_feedrate) if (new_duration > 0) and (duration + new_duration > limit): partial = (limit - duration) / new_duration destination = padd(last_pos, pmul(psub(step.position, last_pos), partial)) duration = limit else: destination = step.position duration += new_duration else: destination = step.position new_path.append(ToolpathSteps.get_step_class_by_action(step.action)(destination)) last_pos = step.position if (step.action == MACHINE_SETTING) and (step.key == "feedrate"): feedrate = step.value if duration >= limit: break return new_path
def intersect_circle_plane(center, radius, direction, triangle): # let n be the normal to the plane n = triangle.normal if pdot(n, direction) == 0: return (None, None, INFINITE) # project onto z=0 n2 = (n[0], n[1], 0) if pnorm(n2) == 0: (cp, d) = triangle.plane.intersect_point(direction, center) ccp = psub(cp, pmul(direction, d)) return (ccp, cp, d) n2 = pnormalized(n2) # the cutter contact point is on the circle, where the surface normal is n ccp = padd(center, pmul(n2, -radius)) # intersect the plane with a line through the contact point (cp, d) = triangle.plane.intersect_point(direction, ccp) return (ccp, cp, d)
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)
def intersect_sphere_point(self, direction, point, start=None): if start is None: start = self.location (ccp, cp, l) = intersect_sphere_point(padd(psub(start, self.location), self.center), self.distance_radius, self.distance_radiussq, direction, point) # offset intersection cl = None if cp: cl = padd(start, pmul(direction, l)) return (cl, ccp, cp, l)
def intersect_torus_plane(center, axis, majorradius, minorradius, direction, triangle): # take normal to the plane n = triangle.normal if pdot(n, direction) == 0: return (None, None, INFINITE) if pdot(n, axis) == 1: return (None, None, INFINITE) # find place on torus where surface normal is n b = pmul(n, -1) z = axis a = psub(b, pmul(z, pdot(z, b))) a_sq = pnormsq(a) if a_sq <= 0: return (None, None, INFINITE) a = pdiv(a, sqrt(a_sq)) ccp = padd(padd(center, pmul(a, majorradius)), pmul(b, minorradius)) # find intersection with plane (cp, l) = triangle.plane.intersect_point(direction, ccp) return (ccp, cp, l)
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
def intersect_cylinder_point(self, direction, point, start=None): if start is None: start = self.location (ccp, cp, l) = intersect_cylinder_point( padd(psub(start, self.location), self.center), self.axis, self.distance_radius, self.distance_radiussq, direction, point) # offset intersection if ccp: cl = padd(start, pmul(direction, l)) return (cl, ccp, cp, l) return (None, None, None, INFINITE)
def intersect_point(self, direction, point): if (direction is not None) and (pnorm(direction) != 1): # calculations will go wrong, if the direction is not a unit vector direction = pnormalized(direction) if direction is None: return (None, INFINITE) denom = pdot(self.n, direction) if denom == 0: return (None, INFINITE) l = -(pdot(self.n, point) - pdot(self.n, self.p)) / denom cp = padd(point, pmul(direction, l)) return (cp, l)
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
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
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)
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
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
def get_shifted_waterline(up_vector, waterline, cutter_location): # Project the waterline and the cutter location down to the slice plane. # This is necessary for calculating the horizontal distance between the # cutter and the triangle waterline. plane = Plane(cutter_location, up_vector) wl_proj = plane.get_line_projection(waterline) if wl_proj.len < epsilon: return None offset = wl_proj.dist_to_point(cutter_location) if offset < epsilon: return wl_proj # shift both ends of the waterline towards the cutter location shift = psub(cutter_location, wl_proj.closest_point(cutter_location)) # increase the shift width slightly to avoid "touch" collisions shift = pmul(shift, 1.0 + epsilon) shifted_waterline = Line(padd(wl_proj.p1, shift), padd(wl_proj.p2, shift)) return shifted_waterline
def get_support_distributed(model, z_plane, average_distance, min_bridges_per_polygon, thickness, height, length, bounds=None, start_at_corners=False): if (average_distance == 0) or (length == 0) or (thickness == 0) or (height == 0): return result = Model() if not hasattr(model, "get_polygons"): model = model.get_waterline_contour( Plane((0, 0, max(model.minz, z_plane)), (0, 0, 1, 'v'))) if model: model = model.get_flat_projection( Plane((0, 0, z_plane), (0, 0, 1, 'v'))) if model and bounds: model = model.get_cropped_model_by_bounds(bounds) if model: polygons = model.get_polygons() else: return None # minimum required distance between two bridge start points avoid_distance = 1.5 * (abs(length) + thickness) if start_at_corners: bridge_calculator = _get_corner_bridges else: bridge_calculator = _get_edge_bridges for polygon in polygons: # no grid for _small_ inner polygons # TODO: calculate a reasonable factor (see below) if polygon.is_closed and (not polygon.is_outer()) \ and (abs(polygon.get_area()) < 25000 * thickness ** 2): continue bridges = bridge_calculator(polygon, z_plane, min_bridges_per_polygon, average_distance, avoid_distance) for pos, direction in bridges: _add_cuboid_to_model(result, pos, pmul(direction, length), height, thickness) return result
def intersect_sphere_point(center, radius, radiussq, direction, point): # line equation # (1) x = p_0 + \lambda * d # sphere equation # (2) (x-x_0)^2 = R^2 # (1) in (2) gives a quadratic in \lambda p0_x0 = psub(center, point) a = pnormsq(direction) b = 2 * pdot(p0_x0, direction) c = pnormsq(p0_x0) - radiussq d = b * b - 4 * a * c if d < 0: return (None, None, INFINITE) if a < 0: l = (-b + sqrt(d)) / (2 * a) else: l = (-b - sqrt(d)) / (2 * a) # cutter contact point ccp = padd(point, pmul(direction, -l)) return (ccp, point, l)
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)
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
def _shift_to_origin(self, position, callback=None): if position != Point3D(0, 0, 0): self.shift(*(pmul(position, -1)), callback=callback)
def get_bezier_lines(points_with_bulge, segments=32): # TODO: add a recursive algorithm for more than two points if len(points_with_bulge) != 2: return [] else: result_points = [] p1, bulge1 = points_with_bulge[0] p2, bulge2 = points_with_bulge[1] if not bulge1 and not bulge2: # straight line return [Line(p1, p2)] straight_dir = pnormalized(psub(p2, p1)) bulge1 = math.atan(bulge1) rot_matrix = Matrix.get_rotation_matrix_axis_angle((0, 0, 1), -2 * bulge1, use_radians=True) dir1_mat = Matrix.multiply_vector_matrix( (straight_dir[0], straight_dir[1], straight_dir[2]), rot_matrix) dir1 = (dir1_mat[0], dir1_mat[1], dir1_mat[2], 'v') if bulge2 is None: bulge2 = bulge1 else: bulge2 = math.atan(bulge2) rot_matrix = Matrix.get_rotation_matrix_axis_angle((0, 0, 1), 2 * bulge2, use_radians=True) dir2_mat = Matrix.multiply_vector_matrix( (straight_dir[0], straight_dir[1], straight_dir[2]), rot_matrix) dir2 = (dir2_mat[0], dir2_mat[1], dir2_mat[2], 'v') # interpretation of bulge1 and bulge2: # /// taken from http://paulbourke.net/dataformats/dxf/dxf10.html /// # The bulge is the tangent of 1/4 the included angle for an arc # segment, made negative if the arc goes clockwise from the start # point to the end point; a bulge of 0 indicates a straight segment, # and a bulge of 1 is a semicircle. alpha = 2 * (abs(bulge1) + abs(bulge2)) dist = pdist(p2, p1) # calculate the radius of the circumcircle - avoiding divide-by-zero if (abs(alpha) < epsilon) or (abs(math.pi - alpha) < epsilon): radius = dist / 2.0 else: # see http://en.wikipedia.org/wiki/Law_of_sines radius = abs(dist / math.sin(alpha / 2.0)) / 2.0 # The calculation of "factor" is based on random guessing - but it # seems to work well. factor = 4 * radius * math.tan(alpha / 4.0) dir1 = pmul(dir1, factor) dir2 = pmul(dir2, factor) for index in range(segments + 1): # t: 0..1 t = float(index) / segments # see: http://en.wikipedia.org/wiki/Cubic_Hermite_spline p = padd( pmul(p1, 2 * t**3 - 3 * t**2 + 1), padd( pmul(dir1, t**3 - 2 * t**2 + t), padd(pmul(p2, -2 * t**3 + 3 * t**2), pmul(dir2, t**3 - t**2)))) result_points.append(p) # create lines result = [] for index in range(len(result_points) - 1): result.append(Line(result_points[index], result_points[index + 1])) return result
def point_with_length_multiply(self, l): return padd(self.p1, pmul(self.dir, l * self.len))