Ejemplo n.º 1
0
    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)
Ejemplo n.º 2
0
    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)
                    ])
                ])
            ]))
Ejemplo n.º 3
0
    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))
Ejemplo n.º 4
0
    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)
Ejemplo n.º 5
0
    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()]),
            ]))
Ejemplo n.º 6
0
 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)
Ejemplo n.º 7
0
 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
Ejemplo n.º 8
0
    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),
                ])
            ]))
Ejemplo n.º 9
0
    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(),
            ]))
Ejemplo n.º 10
0
    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))
Ejemplo n.º 11
0
    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)
                ])
            ]))
Ejemplo n.º 12
0
    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
Ejemplo n.º 13
0
    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)
Ejemplo n.º 14
0
    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])"),
            ]))
Ejemplo n.º 15
0
    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])
Ejemplo n.º 16
0
    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]);")
Ejemplo n.º 17
0
 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)
Ejemplo n.º 18
0
    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
Ejemplo n.º 19
0
    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]);",
            "}",
        ]))
Ejemplo n.º 20
0
 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', [], [], [])])")
Ejemplo n.º 21
0
 def to_scad(self):
     children = [child.to_scad() for child in self._children]
     return ScadObject("union", None, None, children)
Ejemplo n.º 22
0
 def to_scad(self, target):
     children = [target] if target is not None else []
     return ScadObject("scale", [self._xyz], None, children)
Ejemplo n.º 23
0
 def to_scad(self, target):
     children = [target] if target is not None else []
     return ScadObject("rotate", [list(self._xyz)], None, children)
Ejemplo n.º 24
0
 def to_scad(self):
     return ScadObject("sphere", [self._radius], None, None)
Ejemplo n.º 25
0
 def to_scad(self):
     children = [child.to_scad() for child in self._children]
     return ScadObject("difference", None, None, children)
Ejemplo n.º 26
0
 def to_scad(self, target):
     children = [target] if target is not None else []
     return ScadObject("translate", [list(self._vector)], None, children)
Ejemplo n.º 27
0
    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])
            ]))
Ejemplo n.º 28
0
    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"]]')
Ejemplo n.º 29
0
 def to_scad(self):
     # In OpenSCAD, it's called "cube" - even if the sides are not equal.
     return ScadObject("cube", [self._size], None, None)
Ejemplo n.º 30
0
    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