def test_clear_comment(self): both = ScadObject("a", [], [], [ScadObject("b", [], [], [], "b")], "a") inner = ScadObject("a", [], [], [ScadObject("b", [], [], [], "b")]) none = ScadObject("a", [], [], [ScadObject("b", [], [], [])]) self.assertEqual (both.clear_comment(), inner) self.assertNotEqual(both.clear_comment(), none) self.assertEqual (both.clear_comment(recursive=True), none)
def test_to_scad(self): self.ignore_scad_comments = True # General case, with the normal vector in the Y/Z plane self.assertEqual(Plane([0, 1, 1], 2).to_scad(), ScadObject("rotate", None, [('a', 45.0), ('v', [-1.0, 0.0, 0.0])], [ ScadObject("translate", [[0, 0, 2]], None, [ ScadObject("translate", [[-infinity/2, -infinity/2, -infinity]], None, [ ScadObject("cube", [[infinity, infinity, infinity]], None, None) ]) ]) ]))
def test_to_scad(self): sphere = Sphere(2) cube = Cuboid(10, 10, 10) cylinder = Frustum(origin, Z, 11, 11) self.assertEqual( Union([sphere, cube, cylinder]).to_scad(), ScadObject("union", None, None, [ sphere.to_scad(), cube.to_scad(), cylinder.to_scad(), ])) # Empty self.assertEqual( Union([]).to_scad(), ScadObject("union", None, None, None))
def test_ignore_scad_comments(self): scad1 = ScadObject("scad", None, None, None).comment("comment 1") scad2 = ScadObject("scad", None, None, None).comment("comment 2") # Ordinarily, the objects are considered different with self.assertRaises(AssertionError): self.assertEqual(scad1, scad2) # When ignoring comments, the objects are considered equal self.ignore_scad_comments = True self.assertEqual(scad1, scad2) # Ordinarily, the objects are considered different self.ignore_scad_comments = False with self.assertRaises(AssertionError): self.assertEqual(scad1, scad2)
def test_to_scad(self): sphere = Sphere(2) cube = Cuboid(10, 10, 10) cylinder = Frustum(origin, Z, 5, 5) mixed = Union( [Intersection([sphere, cylinder]), Difference([cube, sphere])]) self.assertEqual( mixed.to_scad(), ScadObject("union", None, None, [ ScadObject( "intersection", None, None, [sphere.to_scad(), cylinder.to_scad()]), ScadObject("difference", None, None, [cube.to_scad(), sphere.to_scad()]), ]))
def assertScadObject(self, thing, id, parameters, kw_parameters, children=None): actual = thing.to_scad() expected = ScadObject(id, parameters, kw_parameters, children) self.assertEqual(actual, expected)
def to_scad(self, target): if len(self._transforms) == 0 and target is None: # Special case: this would result in a return value of None. Return # an empty ScadObject instead. return ScadObject(None, None, None, None) else: result = target for transform in self._transforms[::-1]: result = transform.to_scad(result) return result
def test_to_scad(self): r = RotateXyz(60, 30, 15) s = ScaleAxes(1, 2, -1) cube = Cuboid(11, 11, 11) # Simple transform self.assertEqual( Transformed(r, cube).to_scad(), ScadObject("rotate", [[60, 30, 15]], None, [ ScadObject("cube", [[11, 11, 11]], None, None), ])) # Chained transform self.assertEqual( Transformed(Chained([r, s]), cube).to_scad(), ScadObject("rotate", [[60, 30, 15]], None, [ ScadObject("scale", [[1, 2, -1]], None, [ ScadObject("cube", [[11, 11, 11]], None, None), ]) ]))
def test_to_scad(self): sphere = Sphere(2) cube = Cuboid(10, 10, 10) cylinder = Frustum(origin, Z, 11, 11) self.assertEqual( Difference([sphere, cube, cylinder]).to_scad(), ScadObject("difference", None, None, [ sphere.to_scad(), cube.to_scad(), cylinder.to_scad(), ]))
def test_to_scad(self): # Create some transforms r = RotateXyz(60, 30, 15) s = ScaleAxes(1, 2, -1) t = Translate([30, 20, 10]) # Non-empty chained with None target self.assertEqual( Chained([r, s, t]).to_scad(None), ScadObject("rotate", [[60, 30, 15]], None, [ ScadObject( "scale", [[1, 2, -1]], None, [ScadObject("translate", [[30, 20, 10]], None, None)]) ])) # Empty chained with valid target dummy = ScadObject("dummy", None, None, None) self.assertEqual(Chained([]).to_scad(dummy), dummy) # Empty chained with None target self.assertEqual( Chained([]).to_scad(None), ScadObject(None, None, None, None))
def test_to_scad(self): self.ignore_scad_comments = True # Special case: axis aligned with one of the coordinate axes # X self.assertEqual( ScaleAxisFactor(X, 2).to_scad(None), ScadObject("scale", [[2, 1, 1]], None, None)) # Y self.assertEqual( ScaleAxisFactor(Y, 3).to_scad(None), ScadObject("scale", [[1, 3, 1]], None, None)) # Z self.assertEqual( ScaleAxisFactor(Z, 4).to_scad(None), ScadObject("scale", [[1, 1, 4]], None, None)) # -Z self.assertEqual( ScaleAxisFactor(-Z, 4).to_scad(None), ScadObject("scale", [[1, 1, 4]], None, None)) # Special case: factor 1 (aligned or non-aligned) self.assertEqual( ScaleAxisFactor(X, 1).to_scad(None), ScadObject("scale", [[1, 1, 1]], None, None)) self.assertEqual( ScaleAxisFactor(Vector(1, 1, 0), 1).to_scad(None), ScadObject("scale", [[1, 1, 1]], None, None)) # General case, with the scale axis in the Y/Z plane. The scale axis is # rotated to the X axis. # Note that we're comparing floating point numbers here - rounding # errors might become an issue. angle = math.atan(1 / 2) / degree self.assertEqual( ScaleAxisFactor([2, 1, 0], 3).to_scad(None), ScadObject("rotate", None, [ ('a', angle), ('v', [0.0, 0.0, 1.0]) ], [ ScadObject("scale", [[3, 1, 1]], None, [ ScadObject("rotate", None, [('a', angle), ('v', [0.0, 0.0, -1.0])], None) ]) ]))
def to_scad(self): length = (self._cap - self._base).length direction = (self._cap - self._base).normalized() # Create the cylinder if self._base_radius == self._cap_radius: cylinder = ScadObject("cylinder", [length], [('r', self._base_radius)], None) else: cylinder = ScadObject("cylinder", [length], [('r1', self._base_radius), ('r2', self._cap_radius)], None) # Rotate to the correct orientation (skip if it is along the Z axis) if direction != Z: cylinder = RotateFromTo(frm=Z, to=direction, ignore_ambiguity=True).to_scad(cylinder) # Move to the correct position (skip if the base is at the origin) if self._base != Vector(0, 0, 0): cylinder = Translate(self._base).to_scad(cylinder) return cylinder
def to_scad(self, target): # Since OpenSCAD does not have axis/factor scaling, it has to be # translated to corresponding XYZ scales, potentially combined with # rotations. children = [target] if target is not None else [] comment = str(self) # Special case: if the factor is 1, the scale can be expressed as the # unit scale if self._factor == 1: return ScadObject("scale", [[1, 1, 1]], None, children, comment) # Special case: if the axis is aligned with one of the coordinate axes, # the scale can be expressed as an XYZ scale along that axis. if self._axis.collinear(X): return ScadObject("scale", [[self._factor, 1, 1]], None, children, comment) if self._axis.collinear(Y): return ScadObject("scale", [[1, self._factor, 1]], None, children, comment) if self._axis.collinear(Z): return ScadObject("scale", [[1, 1, self._factor]], None, children, comment) # General case return self._equivalent().to_scad(target).comment(comment)
def test_to_tree(self): sphere = ScadObject("sphere" , [0.6] , None, None) cuboid = ScadObject("cube" , [[1, 2, 3]] , None, None) cylinder = ScadObject("cylinder", [4], [("r", 0.5)], None) union = ScadObject("union" , None, None, [sphere, cylinder]) difference = ScadObject("difference", None, None, [union, cuboid]) self.assertEqual(difference.to_tree(), Node("difference()", [ Node("union()", [ Node("sphere(0.6)"), Node("cylinder(4, r = 0.5)"), ]), Node("cube([1, 2, 3])"), ]))
def test_construction(self): # Name only o1 = ScadObject("Dummy", None, None, None) # Name, parameters and keyword parameters o2 = ScadObject("Cylinder", [11], [("r", 2)], None) # Children o3 = ScadObject("Union", None, None, [o2, o1]) # Two children o4 = ScadObject("Union", None, None, [o3, o2]) # One child has children, and o2 appears in the tree twice # Empty without children ScadObject(None, None, None, None) # Empty with children ScadObject(None, None, None, [o2, o1])
def test_to_code(self): sphere = ScadObject("sphere" , [0.6] , None, None) cuboid = ScadObject("cube" , [[1, 2, 3]] , None, None) cylinder = ScadObject("cylinder", [4], [("r", 0.5)], None) union = ScadObject("union" , None, None, [sphere, cylinder]) difference = ScadObject("difference", None, None, [union, cuboid]) chain1 = ScadObject("union", None, None, [cuboid]); chain2 = ScadObject("intersection", None, None, [chain1]); # Simple object self.assertEqual(sphere .to_code(), "sphere(0.6);" ) # Basic self.assertEqual(cuboid .to_code(), "cube([1, 2, 3]);" ) # List parameter self.assertEqual(cylinder.to_code(), "cylinder(4, r = 0.5);") # Multiple parameters # Nested CSG self.assertEqual(difference.to_code(), "\n".join([ "difference() {", " union() {", " sphere(0.6);", " cylinder(4, r = 0.5);", " }", " cube([1, 2, 3]);", "}" ])) # Different indents self.assertEqual(union.to_code(""), "\n".join([ "union() {", "sphere(0.6);", "cylinder(4, r = 0.5);", "}" ])) # No indent self.assertEqual(union.to_code("", " "), "\n".join([ " union() {", " sphere(0.6);", " cylinder(4, r = 0.5);", " }" ])) # Top indent only self.assertEqual(union.to_code(" ", " "), "\n".join([ " union() {", " sphere(0.6);", " cylinder(4, r = 0.5);", " }" ])) # Indent and top indent # Inline self.assertEqual(union.to_code(inline = True), "union() { sphere(0.6); cylinder(4, r = 0.5); }") # With comments sphere_with_comment = ScadObject("sphere", [1], None, None, "A sphere!") union_with_comment = ScadObject("union", None, None, [sphere_with_comment], "A union!\nIt can contain multiple objects.") self.assertEqual(sphere_with_comment.to_code(" ", " "), "\n".join([ " // A sphere!", " sphere(1);", ])) self.assertEqual(union_with_comment.to_code(" ", " "), "\n".join([ " // A union!", " // It can contain multiple objects.", " union() {", " // A sphere!", " sphere(1);", " }" ])) self.assertEqual(sphere_with_comment.to_code(inline = True), "sphere(1);") # Simplify self.assertEqual(chain2.to_code(inline = True ), "intersection() { union() { cube([1, 2, 3]); } }") self.assertEqual(chain2.to_code(inline = True, simplify = True), "intersection() union() cube([1, 2, 3]);")
def to_scad(self, target): children = [target] if target is not None else [] return ScadObject("rotate", None, [("a", self._angle), ("v", list(self._axis))], children)
def test_invalid_construction(self): dummy = ScadObject("Dummy", None, None, None) # Invalid name with self.assertNothingRaised(): ScadObject("Dummy", None, None, None) # Reference with self.assertRaises(TypeError): ScadObject(0 , None, None, None) with self.assertRaises(TypeError): ScadObject([""] , None, None, None) with self.assertRaises(TypeError): ScadObject(("",) , None, None, None) with self.assertRaises(ValueError): ScadObject("" , None, None, None) # Empty with parameters with self.assertNothingRaised(): ScadObject(None , None, None, None) # Reference with self.assertRaises(ValueError): ScadObject(None , [11], None, None) with self.assertRaises(ValueError): ScadObject(None , None, [("r", 2)], None) # Invalid keyword parameters with self.assertNothingRaised(): ScadObject("Dummy", None, [] , None) with self.assertRaises(TypeError): ScadObject("Dummy", None, ("r", 2) , None) # Not in a list with self.assertRaises(TypeError): ScadObject("Dummy", None, [["r", 2]] , None) # Not a tuple with self.assertRaises(TypeError): ScadObject("Dummy", None, [("r", 2, 3)], None) # Not a 2-tuple with self.assertRaises(TypeError): ScadObject("Dummy", None, [("r", )] , None) # Not a 2-tuple with self.assertRaises(TypeError): ScadObject("Dummy", None, [(1, 2)] , None) # Not a string # Invalid children with self.assertNothingRaised(): ScadObject("Dummy", None, None, [dummy]) with self.assertRaises(TypeError): ScadObject("Dummy", None, None, dummy) # Not in a list with self.assertRaises(TypeError): ScadObject("Dummy", None, None, [None]) # Invalid value with self.assertRaises(TypeError): ScadObject("Dummy", None, None, [0]) # Invalid value with self.assertRaises(TypeError): ScadObject("Dummy", None, None, [""]) # Invalid value
def test_to_code_empty(self): sphere = ScadObject("sphere" , [0.6] , None, None) cuboid = ScadObject("cube" , [[1, 2, 3]] , None, None) # Empty SCAD object empty_comment = ScadObject(None, None, None, None) self.assertEqual(empty_comment.to_code(), "\n".join([ ";", ])) # Empty SCAD object with comment empty_comment = ScadObject(None, None, None, None).comment("Empty") self.assertEqual(empty_comment.to_code(), "\n".join([ "// Empty", ";", ])) # Empty SCAD object with children empty_children = ScadObject(None, None, None, [sphere, cuboid]) self.assertEqual(empty_children.to_code(), "\n".join([ "{", " sphere(0.6);", " cube([1, 2, 3]);", "}", ])) # Empty SCAD object with children and comment empty_children_comment = ScadObject(None, None, None, [sphere, cuboid]).comment("Empty") self.assertEqual(empty_children_comment.to_code(), "\n".join([ "// Empty", "{", " sphere(0.6);", " cube([1, 2, 3]);", "}", ]))
def test_repr(self): self.assertRepr(ScadObject("foo", [1, 2], [('a', 3), ('b', 4)], [ScadObject("c1", [], [], []), ScadObject("c2", None, None, None)]), "ScadObject('foo', [1, 2], [('a', 3), ('b', 4)], [ScadObject('c1', [], [], []), ScadObject('c2', [], [], [])])")
def to_scad(self): children = [child.to_scad() for child in self._children] return ScadObject("union", None, None, children)
def to_scad(self, target): children = [target] if target is not None else [] return ScadObject("scale", [self._xyz], None, children)
def to_scad(self, target): children = [target] if target is not None else [] return ScadObject("rotate", [list(self._xyz)], None, children)
def to_scad(self): return ScadObject("sphere", [self._radius], None, None)
def to_scad(self): children = [child.to_scad() for child in self._children] return ScadObject("difference", None, None, children)
def to_scad(self, target): children = [target] if target is not None else [] return ScadObject("translate", [list(self._vector)], None, children)
def test_to_scad(self): self.ignore_scad_comments = True # Cylinder along Z axis self.assertScadObject(Frustum(origin, 5 * Z, 1, 1), "cylinder", [5.0], [('r', 1)]) # Cone along Z axis self.assertScadObject(Frustum(origin, 5 * Z, 1, 0), "cylinder", [5.0], [('r1', 1), ('r2', 0)]) # Frustum along Z axis self.assertScadObject(Frustum(origin, 5 * Z, 2, 1), "cylinder", [5.0], [('r1', 2), ('r2', 1)]) # Cylinder along other axes (X, Y, -X, -Y, -Z) cylinder_scad = ScadObject("cylinder", [5], [('r', 1)], None) self.assertEqual( Frustum(origin, 5 * X, 1, 1).to_scad(), ScadObject("rotate", [], [('a', 90.0), ('v', [0.0, 1.0, 0.0])], [cylinder_scad])) self.assertEqual( Frustum(origin, 5 * Y, 1, 1).to_scad(), ScadObject("rotate", [], [('a', 90.0), ('v', [-1.0, 0.0, 0.0])], [cylinder_scad])) self.assertEqual( Frustum(origin, -5 * X, 1, 1).to_scad(), ScadObject("rotate", [], [('a', 90.0), ('v', [0.0, -1.0, 0.0])], [cylinder_scad])) self.assertEqual( Frustum(origin, -5 * Y, 1, 1).to_scad(), ScadObject("rotate", [], [('a', 90.0), ('v', [1.0, 0.0, 0.0])], [cylinder_scad])) self.assertEqual( Frustum(origin, -5 * Z, 1, 1).to_scad(), ScadObject("rotate", [], [('a', 180.0), ('v', [1.0, 0.0, 0.0])], [cylinder_scad])) # Parallel to Z axis (shifted along X, X/Y, X/Y/Z) cylinder_scad = ScadObject("cylinder", [5], [('r', 1)], None) self.assertEqual( Frustum([1, 0, 0], [1, 0, 5], 1, 1).to_scad(), ScadObject("translate", [[1, 0, 0]], [], [cylinder_scad])) self.assertEqual( Frustum([1, 2, 0], [1, 2, 5], 1, 1).to_scad(), ScadObject("translate", [[1, 2, 0]], [], [cylinder_scad])) self.assertEqual( Frustum([1, 2, 3], [1, 2, 8], 1, 1).to_scad(), ScadObject("translate", [[1, 2, 3]], [], [cylinder_scad])) # Parallel to other axis (X) cylinder_scad = ScadObject("cylinder", [5], [('r', 1)], None) self.assertEqual( Frustum([0, 1, 2], [5, 1, 2], 1, 1).to_scad(), ScadObject("translate", [[0, 1, 2]], [], [ ScadObject("rotate", [], [('a', 90.0), ('v', [0.0, 1.0, 0.0])], [cylinder_scad]) ])) # Not parallel to axis (in the XZ plane without and with translation) cylinder_scad = ScadObject("cylinder", [5 * math.sqrt(2)], [('r', 1)], None) self.assertEqual( Frustum([0, 0, 0], [5, 0, 5], 1, 1).to_scad(), ScadObject("rotate", [], [('a', 45.0), ('v', [0.0, 1.0, 0.0])], [cylinder_scad])) self.assertEqual( Frustum([5, 0, 0], [0, 0, 5], 1, 1).to_scad(), ScadObject("translate", [[5, 0, 0]], [], [ ScadObject("rotate", [], [('a', 45.0), ('v', [0.0, -1.0, 0.0])], [cylinder_scad]) ]))
def test_render_value(self): # Basic data types: integer, float, string, list self.assertEqual(ScadObject.render_value(1 ), "1" ) self.assertEqual(ScadObject.render_value(1.1 ), "1.1" ) self.assertEqual(ScadObject.render_value("Hello" ), '"Hello"' ) self.assertEqual(ScadObject.render_value([1, 2, 3] ), "[1, 2, 3]") # String escaping cr = "\r" lf = "\n" tab = "\t" bs = "\\" # Single backslash dq = '"' # Double quote # Single special characters self.assertEqual(ScadObject.render_value(cr) , r'"\r"') # CR -> "\r" self.assertEqual(ScadObject.render_value(lf) , r'"\n"') # LF -> "\n" self.assertEqual(ScadObject.render_value(tab), r'"\t"') # TAB -> "\t" self.assertEqual(ScadObject.render_value(dq) , r'"\""') # " -> "\"" self.assertEqual(ScadObject.render_value(bs) , r'"\\"') # \ -> "\\" # Combinations of special characters; in particular, with backslash self.assertEqual(ScadObject.render_value(bs + bs), r'"\\\\"') # \\ -> "\\\\" self.assertEqual(ScadObject.render_value(bs + dq), r'"\\\""') # \" -> "\\\"" self.assertEqual(ScadObject.render_value(dq + bs), r'"\"\\"') # "\ -> "\"\\" # Make sure that recursion works, even though it is probably useless self.assertEqual(ScadObject.render_value(["a", "b", "c"]), '["a", "b", "c"]') self.assertEqual(ScadObject.render_value([["a", "b"], ["c", "d"]]), '[["a", "b"], ["c", "d"]]')
def to_scad(self): # In OpenSCAD, it's called "cube" - even if the sides are not equal. return ScadObject("cube", [self._size], None, None)
def test_equality(self): # Equal to itself self.assertEqualToItself(ScadObject("Cylinder", [11], [("r", 2)], None)) # Without children self.assertEqual ( ScadObject("Cylinder", [11], [("r", 2)], None), ScadObject("Cylinder", [11], [("r", 2)], None)) # Equal self.assertNotEqual( ScadObject("Cylinder", [11], [("r", 2)], None), ScadObject("Cone" , [11], [("r", 2)], None)) # Different ID self.assertNotEqual( ScadObject("Cylinder", [11], [("r", 2)], None), ScadObject("Cylinder", [12], [("r", 2)], None)) # Different parameter self.assertNotEqual( ScadObject("Cylinder", [11], [("r", 2)], None), ScadObject("Cylinder", [11], [("d", 2)], None)) # Different keyword parameter key self.assertNotEqual( ScadObject("Cylinder", [11], [("r", 2)], None), ScadObject("Cylinder", [11], [("r", 3)], None)) # Different keyword parameter value self.assertNotEqual( ScadObject("Cylinder", None, [("h", 11), ("r", 2)], None), ScadObject("Cylinder", None, [("r", 3), ("h", 11)], None)) # Different keyword parameter order self.assertNotEqual( ScadObject("Cylinder", [11], [("r", 2)], None, "foo"), ScadObject("Cylinder", [11], [("r", 2)], None, "bar")) # Different comments # With children self.assertEqual( ScadObject("Dummy", [], None, [] ), ScadObject("Dummy", [], None, None)) # [] or None self.assertEqual( ScadObject("Dummy", [], None, [ScadObject("Child", None, None, None)]), ScadObject("Dummy", [], None, [ScadObject("Child", None, None, None)])) # Equal child self.assertNotEqual( ScadObject("Dummy", [], None, []), ScadObject("Dummy", [], None, [ScadObject("Child", None, None, None)])) # No child / one child self.assertNotEqual( ScadObject("Dummy", [], None, [ScadObject("Child", None, None, None)]), ScadObject("Dummy", [], None, [ScadObject("Child", None, None, None), ScadObject("Child", None, None, None)])) # One child / two children self.assertNotEqual( ScadObject("Dummy", [], None, [ScadObject("Child1", None, None, None)]), ScadObject("Dummy", [], None, [ScadObject("Child2", None, None, None)])) # Different children