def from_string(cls, string: str, *args, **kwargs): prev_gui_end = kwargs.get('prev_gui_end', Point2()) prev_gcode_end = kwargs.get('prev_gcode_end', Point2()) cnc_lines = [Line(l) for l in string.strip().split('\n')] assert len(cnc_lines) == 3 line1, line2, line3 = cnc_lines index = line1.gcodes[0].number spill = line1.block.modal_params[1].value speed = line2.gcodes[0].word.value params = line3.gcodes[0].params geom_end_point = Point2(params['X'].value, params['Y'].value) x = geom_end_point.x y = geom_end_point.y return cls(index=index, x=x, y=y, speed=speed, spill=spill, prev_gui_end=prev_gui_end, prev_gcode_end=prev_gcode_end)
def gui_geometry(self): x1, y1 = self._gui_p1.x, self._gui_p1.y x2, y2 = self._gui_p2.x, self._gui_p2.y r = self._r xmid = (x1 + x2) / 2 ymid = (y1 + y2) / 2 d = math.sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2)) a = d / 2 h = math.sqrt(r * r - a * a) xcenter = xmid + (h / d) * (y2 - y1) ycenter = ymid - (h / d) * (x2 - x1) circle = Circle(Point2(xcenter, ycenter), r) ray = Ray2(Point2(xmid, ymid), Point2(xcenter, ycenter)) arc_split_point = circle.intersect(ray).p1 return [ Arc(Point2(xcenter, ycenter), self._r, self._gui_p1, arc_split_point), Arc(Point2(xcenter, ycenter), self._r, arc_split_point, self._gui_p2) ]
def catmull_rom_spline_variants(): points = [ Point2(0,0), Point2(1,1), Point2(2,1), Point2(2,-1), ] controls = control_points(points) # By default, catmull_rom_points() will return a closed smooth shape curve_points_closed = catmull_rom_points(points, close_loop=True) # If `close_loop` is False, it will return only points between the start and # end control points, and make a best guess about tangents for the first and last segments curve_points_open = catmull_rom_points(points, close_loop=False) # By specifying start_tangent and end_tangent, you can change a shape # significantly. This is similar to what you might do with Illustrator's Pen Tool. # Try changing these vectors to see the effects this has on the rightmost curve in the example start_tangent = Vector2(-2, 0) end_tangent = Vector2(3, 0) tangent_pts = [points[0] + start_tangent, *points, points[-1] + end_tangent] tangent_controls = control_points(tangent_pts) curve_points_tangents = catmull_rom_points(points, close_loop=False, start_tangent=start_tangent, end_tangent=end_tangent) closed = polygon(curve_points_closed) + controls opened = polygon(curve_points_open) + controls tangents = polygon(curve_points_tangents) + tangent_controls a = closed + right(3)(opened) + right(10)(tangents) return a
def test_cwarcshortcommand_from_string(): com = CwShortArcToCommand.from_string( string= 'N001 M500 P0\n F12000 \n G02 X-2 Y2 Z0 I.8708286934 J2.8708286934 K0', prev_gui_end=Point2(0, 0), prev_gcode_end=Point2(0, 0)) expect(com.command_type).to_equal(CommandType.CW_ARC_TO_SHORT) expect([com[i] for i in range(10) ]).to_equal([1, 'CW Arc To', -2.0, 2.0, 3.0, 0, 12000, 0, '', '']) expect(com.is_move).to_equal(True) expect(com.disabled).to_equal((8, 9)) gui = com.gui_geometry[-1] expect(len(com.gui_geometry)).to_equal(1) expect(gui.p1).to_equal(Point2(0, 0)) expect(gui.p2).to_equal(Point2(-2.0, 2.0)) expect(type(gui)).to_be(Arc) expect(gui.c.x).almost_equal(0.871, 0.01) expect(gui.c.y).almost_equal(2.871, 0.01) expect(gui.r).almost_equal(3, 0.01) expect(com.length).almost_equal(2.95, 0.01) expect(com.gcode_end_x).to_equal(-2.0) expect(com.gcode_end_y).to_equal(2.0) expect(com.as_gcode).to_equal( 'N001 M500 P0.0\n F12000\n G02 X-2.000 Y2.000 Z0 I0.871 J2.871 K0\n' )
def test_euclidify_non_mutating(self): base_tri = [Point2(0, 0), Point2(10, 0), Point2(0, 10)] next_tri = euclidify(base_tri, Point2) expected = 3 actual = len(base_tri) self.assertEqual(expected, actual, 'euclidify should not mutate its arguments')
def test_cwarcshortcommand_constructor(): com = CwShortArcToCommand(1, 5.0, 3.0, 4.0, 12000, 0.0, Point2(0, 0), Point2(0, 0)) expect(com.command_type).to_equal(CommandType.CW_ARC_TO_SHORT) expect([com[i] for i in range(10) ]).to_equal([1, 'CW Arc To', 5.0, 3.0, 4.0, 0, 12000, 0, '', '']) expect(com.is_move).to_equal(True) expect(com.disabled).to_equal((8, 9)) gui = com.gui_geometry[-1] expect(len(com.gui_geometry)).to_equal(1) expect(gui.p1).to_equal(Point2(0, 0)) expect(gui.p2).to_equal(Point2(5.0, 3.0)) expect(type(gui)).to_be(Arc) expect(gui.c.x).almost_equal(3.909, 0.01) expect(gui.c.y).almost_equal(-0.84, 0.01) expect(gui.r).almost_equal(4, 0.01) expect(com.length).almost_equal(6.53, 0.01) expect(com.gcode_end_x).to_equal(5.0) expect(com.gcode_end_y).to_equal(3.0) expect(com.as_gcode).to_equal( 'N001 M500 P0.0\n F12000\n G02 X5.000 Y3.000 Z0 I3.909 J-0.848 K0\n' )
def test_center_point_arbitrary_begin_obtuse_angle_right_first_q(): begin = LineSegment2(Point2(0, 0), Point2(1, 5)) end = LineSegment2(Point2(1, 5), Point2(5, 7)) diameter = 2 int1, int2, int3, int4 = get_intersects(begin, end, diameter=diameter) expect(int4.x).almost_equal(2.76, 0.01) expect(int4.y).almost_equal(3.64, 0.01)
def test_center_point_vertical_begin_acute_angle_right_first_q(): begin = LineSegment2(Point2(0, 0), Point2(0, 5)) end = LineSegment2(Point2(0, 5), Point2(7, 0)) diameter = 2 int1, int2, int3, int4 = get_intersects(begin, end, diameter=diameter) expect(int4.x).to_equal(2) expect(int4.y).almost_equal(1.11, 0.01)
def test_path_2d(self): base_tri = [Point2(0, 0), Point2(10, 0), Point2(10, 10)] actual = euc_to_arr(path_2d(base_tri, width=2, closed=False)) expected = [ [0.0, 1.0], [9.0, 1.0], [9.0, 10.0], [11.0, 10.0], [11.0, -1.0], [0.0, -1.0] ] self.assertEqual(expected, actual)
def test_center_point_horizontal_begin_right_angle_right_second_q(): begin = LineSegment2(Point2(0, 0), Point2(-5, 0)) end = LineSegment2(Point2(-5, 0), Point2(-5, 5)) diameter = 2 int1, int2, int3, int4 = get_intersects(begin, end, diameter=diameter) expect(int2.x).to_equal(-3) expect(int2.y).to_equal(2)
def test_bezier_points(self): expected = [ Point2(0.00, 0.00), Point2(1.38, 0.62), Point2(2.00, -1.00) ] actual = bezier_points(self.bezier_controls, subdivisions=self.subdivisions) self.assertPointsListsEqual(expected, actual)
def test_center_point_horizontal_begin_acute_angle_right_second_q(): begin = LineSegment2(Point2(0, 0), Point2(-5, 0)) end = LineSegment2(Point2(-5, 0), Point2(-1, 5)) diameter = 2 int1, int2, int3, int4 = get_intersects(begin, end, diameter=diameter) expect(int2.x).almost_equal(-0.83, 0.01) expect(int2.y).almost_equal(2.0, 0.01)
def create_left_buttom_circle(): p0 = switches.get_cap_corner((0, 1), Point2(0.5, -0.5)) p1 = switches.get_cap_corner((1, 1), Point2(0.5, -0.5)) p2 = switches.get_cap_corner((3, 1), Point2(0, -0.62)) circle = utils.three_point_cicle(p0, p1, p2, radius_adjustment=1.5 * mm, segments=1000) right_edge = switches.get_cap_corner(Point2(0, 0), Point2(-0.5, 0.5)).x return circle * sc.square(right_edge * 2, center=True)
def basic_bezier(): # A basic cubic Bezier curve will pass through its first and last # points, but not through the central control points controls = [ Point2(0, 3), Point2(1, 1), Point2(2, 1), Point2(3, 3) ] shape = bezier_polygon(controls, show_controls=True) return shape
def test_bezier_points_raw(self): # Verify that we can use raw sequences of floats as inputs (e.g [(1,2), (3.2,4)]) # rather than sequences of Point2s expected = [ Point2(0.00, 0.00), Point2(1.38, 0.62), Point2(2.00, -1.00) ] actual = bezier_points(self.bezier_controls_raw, subdivisions=self.subdivisions) self.assertPointsListsEqual(expected, actual)
def test_extrude_along_path_2d_scale(self): # verify that we can apply differential x & y scaling path = [[0, 0, 0], [0, 20, 0], [0, 40, 0]] scales_2d = [ Point2(1, 1), Point2(0.5, 1.5), Point2(1.5, 0.5), ] actual = extrude_along_path(tri, path, scales=scales_2d) expected = 'polyhedron(faces=[[0,3,1],[1,3,4],[1,4,2],[2,4,5],[2,5,0],[0,5,3],[3,6,4],[4,6,7],[4,7,5],[5,7,8],[5,8,3],[3,8,6],[9,0,1],[9,1,2],[9,2,0],[10,6,7],[10,7,8],[10,8,6]],points=[[0.0000000000,0.0000000000,0.0000000000],[10.0000000000,0.0000000000,0.0000000000],[0.0000000000,0.0000000000,10.0000000000],[0.0000000000,20.0000000000,0.0000000000],[5.0000000000,20.0000000000,0.0000000000],[0.0000000000,20.0000000000,15.0000000000],[0.0000000000,40.0000000000,0.0000000000],[15.0000000000,40.0000000000,0.0000000000],[0.0000000000,40.0000000000,5.0000000000],[3.3333333333,0.0000000000,3.3333333333],[5.0000000000,40.0000000000,1.6666666667]]);' self.assertEqualOpenScadObject(expected, actual)
def test_path_2d_polygon(self): base_tri = [Point2(0, 0), Point2(10, 0), Point2(10, 10), Point2(0, 10)] poly = path_2d_polygon(base_tri, width=2, closed=True) expected = [(1.0, 1.0), (9.0, 1.0), (9.0, 9.0), (1.0, 9.0), (-1.0, 11.0), (11.0, 11.0), (11.0, -1.0), (-1.0, -1.0)] actual = euc_to_arr(poly.params['points']) self.assertEqual(expected, actual) # Make sure the inner and outer paths in the polygon are disjoint expected = [[0, 1, 2, 3], [4, 5, 6, 7]] actual = poly.params['paths'] self.assertEqual(expected, actual)
def test_catmull_rom_points(self): expected = [ Point2(0.00, 0.00), Point2(0.38, 0.44), Point2(1.00, 1.00), Point2(1.62, 1.06), Point2(2.00, 1.00) ] actual = catmull_rom_points(self.points, subdivisions=self.subdivisions, close_loop=False) self.assertPointsListsEqual(expected, actual)
def basic_catmull_rom(): points = [ Point2(0,0), Point2(1,1), Point2(2,1), Point2(2,-1), ] # In its simplest form, catmull_rom_polygon() will just make a C1-continuous # closed shape. Easy. shape_easy = catmull_rom_polygon(points) # There are some other options as well... shape = catmull_rom_polygon(points, subdivisions=20, extrude_height=5, show_controls=True) return shape_easy + right(3)(shape)
def get_parallel_lines(line, diameter): x1, y1 = line.p1 x2, y2 = line.p2 a = y1 - y2 b = x2 - x1 c = (x1 * y2 - x2 * y1) if b != 0: k = -a / b m = -c / b # y = m*x + c + d*sqrt(1+m^2); par_above = lambda xnew: k * xnew + m + diameter * math.sqrt(1 + k * k) par_below = lambda xnew: k * xnew + m - diameter * math.sqrt(1 + k * k) line_above = Line2(Point2(x1, par_above(x1)), Point2(x2, par_above(x2))) line_below = Line2(Point2(x1, par_below(x1)), Point2(x2, par_below(x2))) else: line_above = Line2(Point2(x1 - diameter, y1), Point2(x2 - diameter, y2)) line_below = Line2(Point2(x1 + diameter, y1), Point2(x2 + diameter, y2)) return line_above, line_below
def test_catmull_rom_points_raw(self): # Verify that we can use raw sequences of floats as inputs (e.g [(1,2), (3.2,4)]) # rather than sequences of Point2s expected = [ Point2(0.00, 0.00), Point2(0.38, 0.44), Point2(1.00, 1.00), Point2(1.62, 1.06), Point2(2.00, 1.00) ] actual = catmull_rom_points(self.points_raw, subdivisions=self.subdivisions, close_loop=False) self.assertPointsListsEqual(expected, actual)
def test_center_point_horizontal_begin_obtuse_angle_right_second_q(): begin = LineSegment2(Point2(0, 0), Point2(-5, 0)) end = LineSegment2(Point2(-5, 0), Point2(-7, 5)) diameter = 2 int1, int2, int3, int4 = get_intersects(begin, end, diameter=diameter) print('\n') print(int1) print(int2) print(int3) print(int4) expect(int1.x).almost_equal(-3.64, 0.01) expect(int1.y).almost_equal(2.0, 0.01)
def _catmull_rom_segment(controls: FourPoints, subdivisions: int, include_last=False) -> List[Point23]: """ Returns `subdivisions` Points between the 2nd & 3rd elements of `controls`, on a quadratic curve that passes through all 4 control points. If `include_last` is True, return `subdivisions` + 1 points, the last being controls[2]. No reason to call this unless you're trying to do something very specific """ pos: Point23 = None positions: List[Point23] = [] num_points = subdivisions if include_last: num_points += 1 p0, p1, p2, p3 = [euclidify(p, Point2) for p in controls] a = 2 * p1 b = p2 - p0 c = 2 * p0 - 5 * p1 + 4 * p2 - p3 d = -p0 + 3 * p1 - 3 * p2 + p3 for i in range(num_points): t = i / subdivisions pos = 0.5 * (a + (b * t) + (c * t * t) + (d * t * t * t)) positions.append(Point2(*pos)) return positions
def extrude_example_xy_scaling() -> OpenSCADObject: num_points = SEGMENTS path_rad = PATH_RAD circle = circle_points(15) path = circle_points(rad=path_rad) # If scales aren't included, they'll default to # no scaling at each step along path. no_scale_obj = make_label('No Scale') no_scale_obj += extrude_along_path(circle, path) # angles: from 0 to 6*Pi angles = list((frange(0, 3 * tau, num_steps=len(path)))) # With a 1-D scale factor, an extrusion grows and shrinks uniformly x_scales = [(1 + cos(a) / 2) for a in angles] x_obj = make_label('1D Scale') x_obj += extrude_along_path(circle, path, scales=x_scales) # With a 2D scale factor, a shape's X & Y dimensions can scale # independently, leading to more interesting shapes # X & Y scales vary between 0.5 & 1.5 xy_scales = [Point2(1 + cos(a) / 2, 1 + sin(a) / 2) for a in angles] xy_obj = make_label('2D Scale') xy_obj += extrude_along_path(circle, path, scales=xy_scales) obj = no_scale_obj + right(3 * path_rad)(x_obj) + right( 6 * path_rad)(xy_obj) return obj
def gcode_geometry(self): # gui_line = self.gui_geometry[-1] # next_line = self.next_segment gui_line = LineSegment2(Point2(7, 10), Point2(7, 15)) next_line = LineSegment2(Point2(7, 15), Point2(0, 20)) right_hand = not is_left(gui_line, next_line.p2) arc_command = 'G02' if right_hand else 'G03' d = 3 intersect1, intersect2, intersect3, intersect4 = get_intersects( gui_line, next_line, diameter=d) inter = intersect4 if right_hand else intersect2 return self.gui_geometry
def _point_along_bez4(p0: Point23, p1: Point23, p2: Point23, p3: Point23, u: float) -> Point2: x = _bez03(u) * p0.x + _bez13(u) * p1.x + _bez23(u) * p2.x + _bez33( u) * p3.x y = _bez03(u) * p0.y + _bez13(u) * p1.y + _bez23(u) * p2.y + _bez33( u) * p3.y return Point2(x, y)
def test_linetowithendcurvecommand_constructor(): com = LineToWithEndCurveCommand(2, 7.0, 12.0, 2, 12000, 0, Point2(0.0, 5.0), Point2(0.0, 5.0)) expect(com.command_type).to_equal(CommandType.LINE_TO_END) expect([com[i] for i in range(10) ]).to_equal([2, 'Line To', 7.0, 12.0, 2.0, '', 12000, 0, '', '']) expect(com.is_move).to_equal(True) # expect(com.length).almost_equal(14.14, 0.01) # expect(com.disabled).to_equal((5, 8, 9)) # gui = com.gui_geometry[-1] # expect(gui.p1).to_equal(Point2(0, 0)) # expect(gui.p2).to_equal(Point2(10, 10)) # gcode = com.gcode_geometry[-1]
def all_solutions_point2(solver: z3.Solver, fillet_center) -> typing.List[Point2]: solutions = [] x, y = fillet_center while solver.check() == z3.sat: m = solver.model() solution = Point2(solution_as_float(m[x]), solution_as_float(m[y])) solutions.append(solution) solver.add((x - solution.x)**2 + (y - solution.y)**2 > 10**(-PRECISION) * 100) return solutions
def _point_along_bez4(p0: Point23List, p1: Point23List, p2: Point23List, p3: Point23List, u: float) -> Point2: p0 = euclidify(p0) p1 = euclidify(p1) p2 = euclidify(p2) p3 = euclidify(p3) x = _bez03(u) * p0.x + _bez13(u) * p1.x + _bez23(u) * p2.x + _bez33( u) * p3.x y = _bez03(u) * p0.y + _bez13(u) * p1.y + _bez23(u) * p2.y + _bez33( u) * p3.y return Point2(x, y)
def create_upper_case(): p0 = switches.get_switch_position((0, 3)) + Point2(-0.5, 0.5) p1 = switches.get_switch_position((2, 3)) + Point2(-0.5, 0.5) p2 = switches.get_switch_position((4, 3)) + Point2(0.5, 0.5) case = utils.three_point_cicle(p0, p1, p2, radius_adjustment=-1.5 * mm, segments=1000) # cut left edge case = case * sc.translate( (utils.big_cutter_length - 0.5, 0, 0))(utils.big_cutter_square) # cut right edge case = case * sc.translate( (-utils.big_cutter_length + 4.5, 0, 0))(utils.big_cutter_square) # cut buttom edge _, buttom_left_switch_y = switches.get_switch_position((0, 1)) case = case * sc.translate( (0, utils.big_cutter_length + buttom_left_switch_y - 0.5, 0))( utils.big_cutter_square) return case