def test_import_scad(self): include_file = self.expand_scad_path("examples/scad_to_include.scad") mod = import_scad(include_file) a = mod.steps(3) actual = scad_render(a) abs_path = a._get_include_path(include_file) expected = f"use <{abs_path}>\n\n\nsteps(howmany = 3);" self.assertEqual(expected, actual) # Make sure this plays nicely with `scad_render()`'s `file_header` arg header = '$fn = 24;' actual = scad_render(a, file_header=header) expected = f"{header}\nuse <{abs_path}>\n\n\nsteps(howmany = 3);" self.assertEqual(expected, actual) # Confirm that we can leave out even non-default arguments in OpenSCAD a = mod.optional_nondefault_arg(); actual = scad_render(a) expected = f'use <{abs_path}>\n\n\noptional_nondefault_arg();' self.assertEqual(expected, actual); # Make sure we throw ValueError on nonexistent imports self.assertRaises(ValueError, import_scad, 'path/doesnt/exist.scad') # Test that we recursively import directories correctly examples = import_scad(include_file.parent) self.assertTrue(hasattr(examples, 'scad_to_include')) self.assertTrue(hasattr(examples.scad_to_include, 'steps'))
def test_import_scad(self): include_file = self.expand_scad_path("examples/scad_to_include.scad") mod = import_scad(include_file) a = mod.steps(3) actual = scad_render(a) abs_path = a._get_include_path(include_file) expected = f"use <{abs_path}>\n\n\nsteps(howmany = 3);" self.assertEqual(expected, actual) # Make sure this plays nicely with `scad_render()`'s `file_header` arg header = '$fn = 24;' actual = scad_render(a, file_header=header) expected = f"{header}\nuse <{abs_path}>\n\n\nsteps(howmany = 3);" self.assertEqual(expected, actual)
def test_thread(self): actual_obj = thread(outline_pts=self.outline, inner_rad=20, pitch=self.tooth_height, length=0.75 * self.tooth_height, segments_per_rot=SEGMENTS, neck_in_degrees=45, neck_out_degrees=45) actual = scad_render(actual_obj) expected = '\n\nrender() {\n' \ '\tintersection() {\n' \ '\t\tpolyhedron(faces = [[0, 1, 3], [1, 4, 3], [1, 2, 4], [2, 5, 4], [0, 5, 2], [0, 3, 5], [3, 4, 6], ' \ '[4, 7, 6], [4, 5, 7], [5, 8, 7], [3, 8, 5], [3, 6, 8], [6, 7, 9], [7, 10, 9], [7, 8, 10], [8, 11, 10], ' \ '[6, 11, 8], [6, 9, 11], [9, 10, 12], [10, 13, 12], [10, 11, 13], [11, 14, 13], [9, 14, 11], [9, 12, 14], ' \ '[12, 13, 15], [13, 16, 15], [13, 14, 16], [14, 17, 16], [12, 17, 14], [12, 15, 17], [15, 16, 18], ' \ '[16, 19, 18], [16, 17, 19], [17, 20, 19], [15, 20, 17], [15, 18, 20], [0, 2, 1], [18, 19, 20]], ' \ 'points = [[14.9900000000, 0.0000000000, -5.0000000000], [19.9900000000, 0.0000000000, 0.0000000000], ' \ '[14.9900000000, 0.0000000000, 5.0000000000], [14.1421356237, 14.1421356237, -3.7500000000], ' \ '[17.6776695297, 17.6776695297, 1.2500000000], [14.1421356237, 14.1421356237, 6.2500000000], ' \ '[0.0000000000, 20.0000000000, -2.5000000000], [0.0000000000, 25.0000000000, 2.5000000000], ' \ '[0.0000000000, 20.0000000000, 7.5000000000], [-14.1421356237, 14.1421356237, -1.2500000000], ' \ '[-17.6776695297, 17.6776695297, 3.7500000000], [-14.1421356237, 14.1421356237, 8.7500000000], ' \ '[-20.0000000000, 0.0000000000, 0.0000000000], [-25.0000000000, 0.0000000000, 5.0000000000], ' \ '[-20.0000000000, 0.0000000000, 10.0000000000], [-14.1421356237, -14.1421356237, 1.2500000000], ' \ '[-17.6776695297, -17.6776695297, 6.2500000000], [-14.1421356237, -14.1421356237, 11.2500000000], ' \ '[-0.0000000000, -14.9900000000, 2.5000000000], [-0.0000000000, -19.9900000000, 7.5000000000], ' \ '[-0.0000000000, -14.9900000000, 12.5000000000]]);\n' \ '\t\tdifference() {\n' \ '\t\t\tcylinder($fn = 8, h = 7.5000000000, r = 25.0100000000);\n' \ '\t\t\tcylinder($fn = 8, h = 7.5000000000, r = 20);\n' \ '\t\t}\n' \ '\t}\n' \ '}' self.assertEqual(expected, actual)
def test_extra_args_to_included_scad(self): include_file = self.expand_scad_path("examples/scad_to_include.scad") mod = import_scad(include_file) a = mod.steps(3, external_var=True) actual = scad_render(a) abs_path = a._get_include_path(include_file) expected = f"use <{abs_path}>\n\n\nsteps(external_var = true, howmany = 3);" self.assertEqual(expected, actual)
def test_use(self): include_file = self.expand_scad_path("examples/scad_to_include.scad") use(include_file) a = steps(3) actual = scad_render(a) abs_path = a._get_include_path(include_file) expected = f"use <{abs_path}>\n\n\nsteps(howmany = 3);" self.assertEqual(expected, actual)
def test_numpy_type(self): try: import numpy numpy_cube = cube(size=numpy.array([1, 2, 3])) expected = '\n\ncube(size = [1,2,3]);' actual = scad_render(numpy_cube) self.assertEqual(expected, actual, 'Numpy SolidPython not rendered correctly') except ImportError: pass
def test_imported_scad_arguments(self): include_file = self.expand_scad_path("examples/scad_to_include.scad") mod = import_scad(include_file) points = mod.scad_points(); poly = polygon(points); actual = scad_render(poly); abs_path = points._get_include_path(include_file) expected = f'use <{abs_path}>\n\n\npolygon(points = scad_points());' self.assertEqual(expected, actual)
def test_include(self): include_file = self.expand_scad_path("examples/scad_to_include.scad") self.assertIsNotNone(include_file, 'examples/scad_to_include.scad not found') include(include_file) a = steps(3) # type: ignore actual = scad_render(a) abs_path = a._get_include_path(include_file) expected = f"include <{abs_path}>\n\n\nsteps(howmany = 3);" self.assertEqual(expected, actual)
def test_use_reserved_words(self): scad_str = '''module reserved_word_arg(or=3){\n\tcube(or);\n}\nmodule or(arg=3){\n\tcube(arg);\n}\n''' fd, path = tempfile.mkstemp(text=True) try: os.close(fd) with open(path, "w") as f: f.write(scad_str) use(path) a = reserved_word_arg(or_=5) actual = scad_render(a) expected = f"use <{path}>\n\n\nreserved_word_arg(or = 5);" self.assertEqual(expected, actual) b = or_(arg=5) actual = scad_render(b) expected = f"use <{path}>\n\n\nor(arg = 5);" self.assertEqual(expected, actual) finally: os.remove(path)
def test(self): call_str = cls + "(" for k, v in args.items(): call_str += f"{k}={v}, " for k, v in kwargs.items(): call_str += f"{k}={v}, " call_str += ')' scad_obj = eval(call_str) actual = scad_render(scad_obj) self.assertEqual(expected, actual)
def test_hole_transform_propagation(self): # earlier versions of holes had problems where a hole # that was used a couple places wouldn't propagate correctly. # Confirm that's still happening as it's supposed to h = hole()(rotate(a=90, v=[0, 1, 0])(cylinder(2, 20, center=True))) h_vert = rotate(a=-90, v=[0, 1, 0])(h) a = cube(10, center=True) + h + h_vert expected = '\n\ndifference(){\n\tunion() {\n\t\tcube(center = true, size = 10);\n\t\trotate(a = -90, v = [0, 1, 0]) {\n\t\t}\n\t}\n\t/* Holes Below*/\n\tunion(){\n\t\trotate(a = 90, v = [0, 1, 0]) {\n\t\t\tcylinder(center = true, h = 20, r = 2);\n\t\t}\n\t\trotate(a = -90, v = [0, 1, 0]){\n\t\t\trotate(a = 90, v = [0, 1, 0]) {\n\t\t\t\tcylinder(center = true, h = 20, r = 2);\n\t\t\t}\n\t\t}\n\t} /* End Holes */ \n}' actual = scad_render(a) self.assertEqual(expected, actual)
def test_color(self): all_args = [ {'c': [1, 0, 0]}, {'c': [1, 0, 0], 'alpha': 0.5}, {'c': "#66F"}, {'c': "Teal", 'alpha': 0.5}, ] expecteds = [ '\n\ncolor(alpha = 1.0000000000, c = [1, 0, 0]);', '\n\ncolor(alpha = 0.5000000000, c = [1, 0, 0]);', '\n\ncolor(alpha = 1.0000000000, c = "#66F");', '\n\ncolor(alpha = 0.5000000000, c = "Teal");', ] for args, expected in zip(all_args, expecteds): col = color(**args) actual = scad_render(col) self.assertEqual(expected, actual)
def test_thread_internal(self): actual_obj = thread(outline_pts=self.outline, inner_rad=20, pitch=2 * self.tooth_height, length=2 * self.tooth_height, segments_per_rot=SEGMENTS, neck_in_degrees=45, neck_out_degrees=45, external=False) actual = scad_render(actual_obj) expected = '''intersection() { polyhedron( convexity=2, faces = [[0, 1, 3], [1, 4, 3], [1, 2, 4], [2, 5, 4], [0, 5, 2], [0, 3, 5], [3, 4, 6], [4, 7, 6], [4, 5, 7], [5, 8, 7], [3, 8, 5], [3, 6, 8], [6, 7, 9], [7, 10, 9], [7, 8, 10], [8, 11, 10], [6, 11, 8], [6, 9, 11], [9, 10, 12], [10, 13, 12], [10, 11, 13], [11, 14, 13], [9, 14, 11], [9, 12, 14], [0, 2, 1], [12, 13, 14]], points = [[25.0100000000, 0.0000000000, 5.0000000000], [20.0100000000, 0.0000000000, 0.0000000000], [25.0100000000, 0.0000000000, -5.0000000000], [0.0000000000, 20.0000000000, 10.0000000000], [0.0000000000, 15.0000000000, 5.0000000000], [0.0000000000, 20.0000000000, 0.0000000000], [-20.0000000000, 0.0000000000, 15.0000000000], [-15.0000000000, 0.0000000000, 10.0000000000], [-20.0000000000, 0.0000000000, 5.0000000000], [-0.0000000000, -20.0000000000, 20.0000000000], [-0.0000000000, -15.0000000000, 15.0000000000], [-0.0000000000, -20.0000000000, 10.0000000000], [25.0100000000, -0.0000000000, 25.0000000000], [20.0100000000, -0.0000000000, 20.0000000000], [25.0100000000, -0.0000000000, 15.0000000000]] ); cylinder($fn = 4, h = 20, r1 = 20, r2 = 20); }''' self.assertEqualNoWhitespace(expected, actual)
def test_custom_iterables(self): from euclid3 import Vector3 class CustomIterable: def __iter__(self): return iter([1, 2, 3]) expected = '\n\ncube(size = [1, 2, 3]);' iterables = [ [1, 2, 3], (1, 2, 3), Vector3(1, 2, 3), CustomIterable(), ] for iterable in iterables: name = type(iterable).__name__ actual = scad_render(cube(size=iterable)) self.assertEqual(expected, actual, f'{name} SolidPython not rendered correctly')
def test_conical_thread_internal(self): actual_obj = thread(outline_pts=self.outline, inner_rad=20, rad_2=40, pitch=self.tooth_height, length=0.75 * self.tooth_height, segments_per_rot=SEGMENTS, neck_in_degrees=45, neck_out_degrees=45, external=False) actual = scad_render(actual_obj) expected = '''intersection(){ polyhedron( convexity=2, faces=[[0,1,3],[1,4,3],[1,2,4],[2,5,4],[0,5,2],[0,3,5],[3,4,6],[4,7,6],[4,5,7],[5,8,7],[3,8,5],[3,6,8],[6,7,9],[7,10,9],[7,8,10],[8,11,10],[6,11,8],[6,9,11],[0,2,1],[9,10,11]], points=[[34.0549376635,0.0000000000,1.7556172079],[27.6176745677,0.0000000000,4.6816458878],[24.6916458878,0.0000000000,-1.7556172079],[0.0000000000,31.3483125545,4.2556172079],[0.0000000000,24.9110494587,7.1816458878],[0.0000000000,21.9850207788,0.7443827921],[-38.0149792212,0.0000000000,6.7556172079],[-31.5777161254,0.0000000000,9.6816458878],[-28.6516874455,0.0000000000,3.2443827921],[-0.0000000000,-54.0549376635,9.2556172079],[-0.0000000000,-47.6176745677,12.1816458878],[-0.0000000000,-44.6916458878,5.7443827921]] ); cylinder($fn=4,h=7.5000000000,r1=20,r2=40); }''' self.assertEqualNoWhitespace(expected, actual)
def test_conical_thread_external(self): actual_obj = thread(outline_pts=self.outline, inner_rad=20, rad_2=40, pitch=self.tooth_height, length=0.75 * self.tooth_height, segments_per_rot=SEGMENTS, neck_in_degrees=45, neck_out_degrees=45, external=True) actual = scad_render(actual_obj) expected = '''intersection(){ polyhedron(convexity=2, faces=[[0,1,3],[1,4,3],[1,2,4],[2,5,4],[0,5,2],[0,3,5],[3,4,6],[4,7,6],[4,5,7],[5,8,7],[3,8,5],[3,6,8],[6,7,9],[7,10,9],[7,8,10],[8,11,10],[6,11,8],[6,9,11],[0,2,1],[9,10,11]], points=[[5.9450623365,0.0000000000,-1.7556172079],[12.3823254323,0.0000000000,-4.6816458878],[15.3083541122,0.0000000000,1.7556172079],[0.0000000000,21.9850207788,0.7443827921],[0.0000000000,28.4222838746,-2.1816458878],[0.0000000000,31.3483125545,4.2556172079],[-28.6516874455,0.0000000000,3.2443827921],[-35.0889505413,0.0000000000,0.3183541122],[-38.0149792212,0.0000000000,6.7556172079],[-0.0000000000,-25.9450623365,5.7443827921],[-0.0000000000,-32.3823254323,2.8183541122],[-0.0000000000,-35.3083541122,9.2556172079]] ); difference(){ cylinder($fn=4,h=7.5000000000,r1=29.3732917757,r2=49.3732917757); cylinder($fn=4,h=7.5000000000,r1=20,r2=40); } }''' self.assertEqualNoWhitespace(expected, actual)
def test_neck_in_out_degrees(self): # Non-specified neck_in_degrees and neck_out_degrees would crash prior # to the fix for https://github.com/SolidCode/SolidPython/issues/92 actual_obj = thread(outline_pts=self.outline, inner_rad=20, pitch=self.tooth_height, length=0.75 * self.tooth_height, segments_per_rot=SEGMENTS, neck_in_degrees=45, neck_out_degrees=0) actual = scad_render(actual_obj) expected = '''intersection(){ polyhedron( convexity=2, faces=[[0,1,3],[1,4,3],[1,2,4],[2,5,4],[0,5,2],[0,3,5],[3,4,6],[4,7,6],[4,5,7],[5,8,7],[3,8,5],[3,6,8],[6,7,9],[7,10,9],[7,8,10],[8,11,10],[6,11,8],[6,9,11],[0,2,1],[9,10,11]], points=[[14.9900000000,0.0000000000,-5.0000000000],[19.9900000000,0.0000000000,0.0000000000],[14.9900000000,0.0000000000,5.0000000000],[0.0000000000,20.0000000000,-2.5000000000],[0.0000000000,25.0000000000,2.5000000000],[0.0000000000,20.0000000000,7.5000000000],[-20.0000000000,0.0000000000,0.0000000000],[-25.0000000000,0.0000000000,5.0000000000],[-20.0000000000,0.0000000000,10.0000000000],[-0.0000000000,-20.0000000000,2.5000000000],[-0.0000000000,-25.0000000000,7.5000000000],[-0.0000000000,-20.0000000000,12.5000000000]] ); difference(){ cylinder($fn=4,h=7.5000000000,r1=25.0100000000,r2=25.0100000000); cylinder($fn=4,h=7.5000000000,r1=20,r2=20); } }''' self.assertEqualNoWhitespace(expected, actual)
def test_thread_internal(self): actual_obj = thread(outline_pts=self.outline, inner_rad=20, pitch=2 * self.tooth_height, length=2 * self.tooth_height, segments_per_rot=SEGMENTS, neck_in_degrees=45, neck_out_degrees=45, external=False) actual = scad_render(actual_obj) expected = '\n\nrender() {\n' \ '\tintersection() {\n' \ '\t\tpolyhedron(faces = [[0, 1, 3], [1, 4, 3], [1, 2, 4], [2, 5, 4], [0, 5, 2], [0, 3, 5], ' \ '[3, 4, 6], [4, 7, 6], [4, 5, 7], [5, 8, 7], [3, 8, 5], [3, 6, 8], [6, 7, 9], [7, 10, 9], [7, 8, 10], ' \ '[8, 11, 10], [6, 11, 8], [6, 9, 11], [9, 10, 12], [10, 13, 12], [10, 11, 13], [11, 14, 13], ' \ '[9, 14, 11], [9, 12, 14], [12, 13, 15], [13, 16, 15], [13, 14, 16], [14, 17, 16], [12, 17, 14], ' \ '[12, 15, 17], [15, 16, 18], [16, 19, 18], [16, 17, 19], [17, 20, 19], [15, 20, 17], [15, 18, 20], ' \ '[18, 19, 21], [19, 22, 21], [19, 20, 22], [20, 23, 22], [18, 23, 20], [18, 21, 23], [21, 22, 24], ' \ '[22, 25, 24], [22, 23, 25], [23, 26, 25], [21, 26, 23], [21, 24, 26], [0, 2, 1], [24, 25, 26]], ' \ 'points = [[25.0100000000, 0.0000000000, -5.0000000000], [20.0100000000, 0.0000000000, 0.0000000000], ' \ '[25.0100000000, 0.0000000000, 5.0000000000], [14.1421356237, 14.1421356237, -2.5000000000], ' \ '[10.6066017178, 10.6066017178, 2.5000000000], [14.1421356237, 14.1421356237, 7.5000000000], ' \ '[0.0000000000, 20.0000000000, 0.0000000000], [0.0000000000, 15.0000000000, 5.0000000000], ' \ '[0.0000000000, 20.0000000000, 10.0000000000], [-14.1421356237, 14.1421356237, 2.5000000000], ' \ '[-10.6066017178, 10.6066017178, 7.5000000000], [-14.1421356237, 14.1421356237, 12.5000000000],' \ ' [-20.0000000000, 0.0000000000, 5.0000000000], [-15.0000000000, 0.0000000000, 10.0000000000], ' \ '[-20.0000000000, 0.0000000000, 15.0000000000], [-14.1421356237, -14.1421356237, 7.5000000000],' \ ' [-10.6066017178, -10.6066017178, 12.5000000000], [-14.1421356237, -14.1421356237, 17.5000000000], ' \ '[-0.0000000000, -20.0000000000, 10.0000000000], [-0.0000000000, -15.0000000000, 15.0000000000],' \ ' [-0.0000000000, -20.0000000000, 20.0000000000], [14.1421356237, -14.1421356237, 12.5000000000],' \ ' [10.6066017178, -10.6066017178, 17.5000000000], [14.1421356237, -14.1421356237, 22.5000000000],' \ ' [25.0100000000, -0.0000000000, 15.0000000000], [20.0100000000, -0.0000000000, 20.0000000000], ' \ '[25.0100000000, -0.0000000000, 25.0000000000]]);\n' \ '\t\tcylinder($fn = 8, h = 20, r = 20);\n' \ '\t}\n' \ '}' self.assertEqual(expected, actual)
def test_separate_part_hole(self): # Make two parts, a block with hole, and a cylinder that # fits inside it. Make them separate parts, meaning # holes will be defined at the level of the part_root node, # not the overall node. This allows us to preserve holes as # first class space, but then to actually fill them in with # the parts intended to fit in them. b = cube(10, center=True) c = cylinder(r=2, h=12, center=True) p1 = b - hole()(c) # Mark this cube-with-hole as a separate part from the cylinder p1 = part()(p1) # This fits in the hole. If p1 is set as a part_root, it will all appear. # If not, the portion of the cylinder inside the cube will not appear, # since it would have been removed by the hole in p1 p2 = cylinder(r=1.5, h=14, center=True) a = p1 + p2 expected = '\n\nunion() {\n\tdifference(){\n\t\tdifference() {\n\t\t\tcube(center = true, size = 10);\n\t\t}\n\t\t/* Holes Below*/\n\t\tunion(){\n\t\t\tcylinder(center = true, h = 12, r = 2);\n\t\t} /* End Holes */ \n\t}\n\tcylinder(center = true, h = 14, r = 1.5000000000);\n}' actual = scad_render(a) self.assertEqual(expected, actual)
def test_infix_difference(self): a = cube(2) b = sphere(2) expected = '\n\ndifference() {\n\tcube(size = 2);\n\tsphere(r = 2);\n}' actual = scad_render(a - b) self.assertEqual(expected, actual)
def test_infix_intersection(self): a = cube(2) b = sphere(2) expected = '\n\nintersection() {\n\tcube(size = 2);\n\tsphere(r = 2);\n}' actual = scad_render(a * b) self.assertEqual(expected, actual)
def test_background(self): a = cube(10) expected = '\n\n%cube(size = 10);' actual = scad_render(background(a)) self.assertEqual(expected, actual)
def test_explicit_hole(self): a = cube(10, center=True) + hole()(cylinder(2, 20, center=True)) expected = '\n\ndifference(){\n\tunion() {\n\t\tcube(center = true, size = 10);\n\t}\n\t/* Holes Below*/\n\tunion(){\n\t\tcylinder(center = true, h = 20, r = 2);\n\t} /* End Holes */ \n}' actual = scad_render(a) self.assertEqual(expected, actual)
def test_root(self): a = cube(10) expected = '\n\n!cube(size = 10);' actual = scad_render(root(a)) self.assertEqual(expected, actual)
def test_disable(self): a = cube(10) expected = '\n\n*cube(size = 10);' actual = scad_render(disable(a)) self.assertEqual(expected, actual)
def test_debug(self): a = cube(10) expected = '\n\n#cube(size = 10);' actual = scad_render(debug(a)) self.assertEqual(expected, actual)