# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['muscad', 'muscad.utils', 'muscad.vitamins']

package_data = \
{'': ['*']}

setup_kwargs = {
    'name': 'muscad',
    'version': '0.5.1',
    'description': 'OpenSCAD, with a Python Spice',
    'long_description': 'MuSCAD: OpenSCAD with a Python spice\n-------------------------------------\n\nMuSCAD lets you write simple, readable, Pythonic code, that automatically translates into valid OpenSCAD code.\nIt is loosely inspired by\n`SolidPython <https://github.com/SolidCode/SolidPython>`__, which is itself based on\nPhillip Tiefenbacher\'s openscad module, found on\n`Thingiverse <http://www.thingiverse.com/thing:1481>`__.\n\nHere\'s a simple example. This simple Python code::\n\n    from muscad import Cube, Sphere, Cylinder\n    print(\n        Cube(10, 10, 10).down(10)\n      - Sphere(d=15)\n      + Cylinder(h=15, d=12).leftward(16)\n    )\n\nWill output this OpenSCAD code:\n\n::\n\n    union() {\n        difference() {\n            translate(v=[0, 0, -10]) {\n                cube(size=[10, 10, 10]);\n            }\n            sphere(d=15);\n        }\n        translate(v=[-16, 0, 0]) {\n            cylinder(h=15, d=12, $fn=94);\n        }\n    }\n\nObviously this simple example is not enough to show what MuSCAD allows you to do with ease.\nHere are a few real-life 3D printer parts designed with MuSCAD:\n\n.. figure:: https://gitlab.com/guillp/muscad/-/raw/master/doc/images/z_stepper_mount.png\n\n   A stepper motor attachment. `See the OpenSCAD code here <https://gitlab.com/guillp/muscad/-/blob/master/tests/target_scad_files/z_stepper_mount.scad>`_ and `check the MuSCAD code here <https://gitlab.com/guillp/muscad/-/blob/master/examples/hdw/z_axis/z_stepper_mount.py>`_\n\n.. figure:: https://gitlab.com/guillp/muscad/-/raw/master/doc/images/feet.png\n\n   A feet for 3030 extrusion based printers. `Check the OpenSCAD code here <https://gitlab.com/guillp/muscad/-/blob/master/tests/target_scad_files/feet.scad>`_ and `the MuSCAD code here <https://gitlab.com/guillp/muscad/-/blob/master/examples/hdw/misc/feet.py>`_\n\n.. figure:: https://gitlab.com/guillp/muscad/-/raw/master/doc/images/z_bed_mount.png\n\n   A heating bed attachment for HyperCube-like printers. `Here is OpenSCAD code <https://gitlab.com/guillp/muscad/-/blob/master/tests/target_scad_files/z_bed_mount.scad>`_ and `here is the MuSCAD code <https://gitlab.com/guillp/muscad/-/blob/master/examples/hdw/z_axis/z_bed_mount.py>`_\n\n\nAdvantages\n=============\n\nThe main benefit of using MuSCAD is that you actually write Python\ncode, so you get all the advantages of that language, which include:\n\n - you can use all Python features, including all syntactic sugar like built-in\n   dict/list comprehensions, external modules such as text parsing, image \n   recognition, etc. which enables very original ways of creating objects.\n - you can use a real IDE, possibly providing code completion, code\n   validity/typos checks, etc. which makes it way easier and faster to write code\n   compared to most generally poor tools used to write OpenSCAD code.\n\nThe second advantage, compared to SolidPython and other similar modules, is that\nMuSCAD lets you write Pythonic, easy to read code, and does not try to emulate\nthe way you would write the same code in OpenSCAD. It enables features that are not part\nof OpenSCAD, like automatic calculation of object sizes, relative alignment between objects,\nand more.\nIn that sense, MuSCAD syntax is fundamentally different compared to OpenSCAD/SolidPython.\nThose differences are explained in the next sections.\n\nInstalling MuSCAD\n===================\n\nMuSCAD requires Python 3.6 or more and has no other dependency.\nYou obviously need to install OpenSCAD to view the generated files.\n\nInstall via `PyPI <https://pypi.python.org/pypi/muscad>`__::\n\n       pip install muscad\n\nUsage with Jupyter Notebook or Jupyter Lab is recommended.\n\nUsing MuSCAD\n-------------\n\nMuSCAD includes classes for all OpenSCAD primitives. You can instantiate those\nclasses, then apply transformations using methods on those instances, and\nfinally apply boolean operations using the ``+``, ``-`` and ``&`` operators.\n\nTo generate the equivalent OpenSCAD code, simply ``print()`` the resulting object.\n\nExample::\n\n  from muscad import Sphere\n  hollow_sphere = Sphere(10) - Sphere(8)\n  print(hollow_sphere)\n\nwill output: ::\n\n  difference() {\n    sphere(d=10, $fn=78);\n    sphere(d=8, $fn=62);\n  }\n\nSimply copy/paste that output into OpenSCAD, and it will render the resulting object.\nTo make things easier, you can programmatically save the output code to a file directly, and have that file opened\nin OpenSCAD (if the openscad executable is in your ``PATH``)::\n  \n  hollow_sphere.render_to_file(\'hollow_sphere.scad\', openscad=True)\n\nIf you only want to save the file, without (re)opening OpenSCAD, omit the ``openscad`` parameter or set it to `False`.\nYou may also set the environment variable ``MUSCAD_NO_OPENSCAD`` to inhibit MuSCAD from opening OpenSCAD automatically.\n\nUsing Jupyter\n==============\n\nIf you use Jupyter Notebook, it will automatically display the OpenSCAD code for any object in a cell.\n\nHow it works\n---------------\nNow, to understand how MuSCAD generates the OpenSCAD code, let\'s go over the sample Python code above, line by line::\n\n  from muscad import Sphere\n\nThis import the primitive `Sphere` from the top level module ``muscad``. All other primitives can be imported from there.\n::\n\n  hollow_sphere = Sphere(10) - Sphere(8)\n\nThis generate a MuSCAD object called ``hollow_sphere``, made from the difference of 2 spheres (a smaller one removed from a bigger one).\n\nWhat is a ``Sphere`` ? It is obviously the equivalent of OpenSCAD\'s ``sphere``. Like all other primitives, a ``Sphere`` is an instance of an ``Object``::\n\n   from muscad import Object\n   assert isinstance(Sphere(10), Object)\n\nWhy is that important ? Because there is a lot you can do with a MuSCAD ``Object``, like rendering it, applying transformations to it, using it \nin boolean operations, aligning it to absolute coordinates, etc. But we\'ll see about that later.\n\nNow, what is this ``hollow_box`` object? It is a ``Difference``::\n  \n  from muscad import Difference\n  assert type(hollow_sphere) == Difference\n\nThat ``Difference`` is itself a subclass of ``Object``::\n\n  from muscad import Object\n  assert isinstance(hollow_sphere) == Object\n\nSo you can do to that difference everything you can do to a primitive. Obviously, you can render the OpenSCAD code, that\'s what happens when we call::\n\n  print(hollow_sphere)\n\nresults in::\n\n    difference() {\n      sphere(d=10, $fn=78);\n      sphere(d=8, $fn=62);\n    }\n\nNote that when rendering a ``Sphere``, the ``$fn`` parameter, which indicates how many segments OpenSCAD must use to render that sphere) is produced automatically by MuSCAD to create a good-looking round shape.\nIn the usual where you want your round shapes to actually appear round, just let MuSCAD handle this for you. Otherwise, you can override that number with the ``segments`` parameter to Sphere::\n\n    Sphere(20, segments=6)\n\nPrimitives\n=============\n\nAll available primitives from OpenSCAD are available in MuSCAD::\n\n  # 3D Primitives\n  Cube(width, depth, height)  # cube\n  Cylinder(h, d, d2=None, segments="auto")  # cylinder\n  Sphere(d, segments="auto")  # sphere\n  Polyhedron(points, faces, convexity=1)  # polyhedron\n\n  # 2D Primitives\n  Circle(d, segments="auto")  # circle\n  Square(width, depth)  # square\n  Text(text, size=10, font=None, halign=None, valign=None, spacing=None, direction=None, language=None, script=None, segments=None)  # text\n  Polygon(*points, path=None, convexity=None)  # polygon\n\nNote that MuSCAD includes a high level ``Volume`` class that is basically a ``Cube`` but with a lot of added features, see below.\n\n\nBoolean Operations\n====================\n\nBoolean operations `union()`, `difference()` and `intersection()` are applied using the operators `+`, `-` and `&` respectively::\n\n    Sphere(d=5) + Cube(10, 2, 1) + Cube(3, 3, 3).leftward(4)  # union\n    Sphere(d=5) - Cube(10, 2, 1) - Cube(1, 1, 1)  # difference\n    Sphere(d=5) & Cube(10, 2, 1) & Cube(1, 4, 2)  # intersection\n\nBeware that standard Python operator precedence applies: ``+`` and ``-`` applies before ``&``.\nSo the following codes produce different results::\n\n    Sphere(d=5) & Cube(10, 2, 1) & Cube(1, 1, 1) + Sphere(50)\n\nand::\n\n    (Sphere(d=5) & Cube(10, 2, 1) & Cube(1, 1, 1)) + Sphere(50)\n\nYou can also use a more traditional paradigm::\n\n    from muscad import Union, Difference, Intersection\n    union = Union( Sphere(d=5), Cube(10, 2, 1), Cube(3, 3, 3).leftward(4) )\n    difference = Difference( Sphere(d=5), Cube(10, 2, 1), Cube(1, 1, 1) )\n    intersection = Intersection( Sphere(d=5), Cube(10, 2, 1), Cube(1, 4, 2) )\n\nAs already mentioned above, the result of a boolean operation is itself a MuSCAD `Object`, so you can keep applying new boolean operations or transformations to it.\n\nTransformations\n================\n\nYou can apply transformations to any MuSCAD ``Object`` by calling the transformation methods `.translate()`, `.rotate()`, etc.\nHere we translate a Sphere 10 mm upwards::\n\n  print(Sphere(10).translate(z=10))\n\nThis will give the following OpenSCAD code::\n\n  translate(v=[0, 0, 10]) \n  sphere(d=10, $fn=78);\n\nAny ``Object``, including results of boolean operations, or transformed objects, can be applied a transformation.\nSo you can obviously chain multiple methods like this::\n\n  Cube(10, 10, 10).translate(15, 15, 0).rotate(0, 45, 0)\n\nMuSCAD includes helpers methods for single axis translations and rotations.\nUsing those helpers, the code just above is equivalent to::\n\n  Cube(10, 10, 10).rightward(15).up(15).y_rotate(45)\n\nNote that MuSCAD will automatically merge multiple chained translations or rotations on the same object\n(this however has no effect on the rendered part).\nSo both the lines above will result in the same generated OpenSCAD code (notice that there\nis a single translation on the cube combining both translations rightward and upward, instead of several)::\n\n   rotate(a=[0, 45, 0]) {\n       translate(v=[15, 0, 15]) {\n           cube(size=[10, 10, 10]);\n       }\n   }\n\nAll available transformation methods are as follow::\n\n  .translate(x=0, y=0, z=0)  # applies a Translation\n  .rightward(dist)  # applies a Translation to the right\n  .leftward(dist)   # applies a Translation to the left\n  .forward(dist)  # applies a Translation to the front\n  .backward(dist) # applies a Translation to the back\n  .up(dist)  # applies a Translation upwards\n  .down(dist)  # applies a Translation downwards\n\n  .rotate(x=0, y=0, z=0)  # applies a Rotation\n  .x_rotate(angle)  # applies a Rotation on the X axis\n  .y_rotate(angle)  # applies a Rotation on the Y axis\n  .z_rotate(angle)  # applies a Rotation on the Z axis\n  .left_to_bottom()  # turn left face to bottom, alias for .y_rotate(-90)\n  .left_to_top()  # turn left face to top, alias for .y_rotate(90)\n  .left_to_front()  # alias for .z_rotate(-90)\n  .left_to_back()  # alias for .z_rotate(90)\n  .upside_down(y_axis=False)  # alias for .x_rotate(180) if y_axis==False else .y_rotate(180)\n\n  .scale(x=0, y=0, z=0)  # applies a Scaling transformation\n  .resize(x=0, y=0, z=0)  # applies a Resizing transformation\n\n  .mirror(x=0, y=0, z=0)  # applies a Mirroring transformation\n  .x_mirror(center=0)  # applies a mirroring on X axis, offset by `center`\n  .y_mirror(center=0)  # applies a mirroring on Y axis, offset by `center`\n  .z_mirror(center=0)  # applies a mirroring on Z axis, offset by `center`\n\n  .linear_extrude(height, center=False, convexity=10, twist=0.0, slices=None, scale=1.0, segments="auto")  # applies a LinearExtrusion\n  .z_linear_extrude(distance=None, bottom=None, center_z=None, top=None, convexity=10, twist=0.0, slices=None, scale=1.0, segments="auto") # helper to do a LinearExtrusion on the Z axis\n  .y_linear_extrude(distance=None, back=None, center_y=None, front=None, convexity=10, twist=0.0, slices=None, scale=1.0, segments="auto") # helper to do a LinearExtrusion on the Y axis\n  .x_linear_extrude(distance=None, left=None, center_x=None, right=None, convexity=10, twist=0.0, slices=None, scale=1.0, segments="auto") # helper to do a LinearExtrusion on the X axis\n\n  .rotational_extrude(angle=360, convexity=None, segments="auto")  # applies a RotationalExtrusion\n\n  .color(colorname)  # change the object color\n\n  .slide(x=0, y=0, z=0)  # applies a Slide transformation\n\nBounding Box and Alignment\n------------------------------\n\nSo far we have seen the equivalent of what you can do with OpenSCAD, with a different syntax but without much added value.\nHere is one of the best added value of MuSCAD: it knows the size and position of any ``Object``, which make it very easy to position\nor reposition the object at absolute coordinates, or even at a relative position compared to another ``Object``. This is called `alignment`.\n\nFirst, let\'s understand the dimension of an ``Object`` by creating a ``Cube`` (which is badly named in OpenSCAD since it can have different dimensions on each axis)::\n\n  from muscad import Cube\n  box = Cube(20, 30, 50)\n\nMuSCAD primitives are always created centered, so our `box` with a width of 20 extends from -10 to +10 on the X axis. MuSCAD knows about that::\n\n  assert box.left == -10\n  assert box.right == 10\n\nSame on the Y and Z axis::\n  \n  assert box.back == -15\n  assert box.front == 15\n  assert box.bottom == -25\n  assert box.top == 25\n\nThe box formed by the leftmost, rightmost, back, front, bottom and top coordinates of an object is called the `bounding box`.\n\nSince MuSCAD knows the lower and upper bounds on all axis, it can compute the center as well. Since our ``box`` was created centered, the center is 0 on all axis::\n\n  assert box.center_x == 0\n  assert box.center_y == 0\n  assert box.center_z == 0\n\nAnd obviously it can compute the object width, depth and height (which is easy enough in the case of a ``Cube``, since they are directly declared when the ``Cube`` is created)::\n\n  assert box.width == 20\n  assert box.depth == 30\n  assert box.height == 50\n\nMuSCAD is able to calculate the bounding box of all primitives, as well as the results of boolean operations or transformed objects (with some limitations)::\n\n  assert Cube(10, 20, 30).up(15).bottom == 0\n  assert (\n       Cube(10, 20, 30).leftward(5)\n     + Cube(5, 40, 10).up(40)\n  ).height == 60\n\n\nThe limitations are as follow:\n  - For rotated objects, MuSCAD is only able to compute the bounding box on an axis rotated by a multiple of 90°.\n  - For Differences, the bounding box is that of the first Object (the base object from which all other objects are substracted from), even if a substracted object actually reduces that box.\n  - For Intersections, the bounding box is the intersection of the bounding box of all intersected objects. This won\'t work accurately from most object shapes, but should be enough for most cases.\n\nSince MuSCAD knows the bounding boxes of all objects it creates, it can also reposition them at absolute coordinates.\nThat is done using ``.align()``. Here we create a box and align its left, back and bottom sides to 0::\n\n  aligned_box = Cube(10, 10, 10).align(left=0, back=0, bottom=0)\n  assert aligned_box.left == aligned_box.back == aligned_box.bottom == 0\n  assert aligned_box.right == aligned_box.front == aligned_box.top == 10\n\nAs you can expect, alignment is done using a translation in the resulting OpenSCAD code::\n\n  translate(v=[5.0, 5.0, 5.0]) \n  box(size=[10, 10, 10], center=true);\n\nYou can align an ``Object`` on the ``left``, ``right`` and ``center_x`` on the X axis, ``back``, ``front`` and ``center_y`` on the Y axis, and ``bottom``, ``top``\nand ``center_z`` on the Z axis.\nSince we can align objects to arbitrary coordinates, and we can get the bounding box coordinates for all objects, we can\nalso align objects relatively to each other. Here we create a tower of 3 colored cubes::\n\n      big_cube = Cube(40, 40, 40).color(\'blue\')\n      medium_cube = (\n        Cube(30, 30, 30)\n        .color(\'red\')\n        .align(center_x=big_cube.center_x, center_y=big_cube.center_y, bottom=big_cube.top)\n      )\n      small_cube = (\n        Cube(20, 20, 20)\n        .color(\'yellow\')\n        .align(center_x=medium_cube.center_x, center_y=medium_cube.center_y, bottom=medium_cube.top)\n      )\n      print(big_cube + medium_cube + small_cube)\n\nThis gives the resulting OpenSCAD code::\n\n  union() {\n    color("blue") \n    cube(size=[40, 40, 40], center=true);\n    translate(v=[0.0, 0.0, 35.0]) \n    color("red") \n    cube(size=[30, 30, 30], center=true);\n    translate(v=[0.0, 0.0, 60.0]) \n    color("yellow") \n    cube(size=[20, 20, 20], center=true);\n  }\n\nIf you decide later to change the position of the big cube, you only have to change its alignment in the first line of Python code,\nand the medium and small cube will automatically stay on top of it in the generated OpenSCAD code. That is relative positioning, something that \nwas very hard to do with OpenSCAD, because you had to track the position of objects yourself with variables. MuSCAD does that position tracking for you::\n\n  big_cube = Cube(40, 40, 40).color(\'blue\').align(left=10, back=10, bottom=10) # added some alignment for the first cube, the rest of the code is untouched\n  medium_cube = Cube(30, 30, 30).align(center_x=big_cube.center_x, center_y=big_cube.center_y, bottom=big_cube.top).color(\'red\')\n  small_cube = Cube(20, 20, 20).align(center_x=medium_cube.center_x, center_y=medium_cube.center_y, bottom=medium_cube.top).color(\'yellow\')\n  print(big_cube + medium_cube + small_cube)\n\n  union() {\n    color("blue") \n    translate(v=[30.0, 30.0, 30.0]) \n    box(size=[40, 40, 40], center=true);\n    color("red") \n    translate(v=[30.0, 30.0, 65.0]) \n    box(size=[30, 30, 30], center=true);\n    color("yellow") \n    translate(v=[30.0, 30.0, 90.0]) \n    box(size=[20, 20, 20], center=true);\n  }\n\nEpsilon and Tolerance\n=========================\n\nOften when aligning parts, you want to take into account the tolerance margin for your printer.\nAn appropriate way to do that is to define your tolerance as a constant named T, and add it or substract it whenever needed.\nMuSCAD includes a default T which is 0.1 mm. If you need a bigger tolerance somewhere, there is a default TT and TTT values\nthat are 0.2 mm and 0.3 mm respectively.\n\nAlso, in order to avoid the OpenSCAD "bug" when 2 surfaces are exactly on the same plane, you might want to offset one by a\nvery small value, called an "Epsilon" (something like 0.01 mm). Define this value as a constant named E and add or substract\nwherever needed. MuSCAD includes a default E value of 0.02 mm, and a "double epsilon" value EE of 0.04 mm.::\n\n    from muscad import E, EE, T, TT, TTT\n\nModifiers\n---------------\n\nYou can apply OpenSCAD modifiers #, %, * and !, by calling the methods ``.debug()``, ``.background()``, ``.disable()``, ``.root()`` respectively::\n\n  debugged_object = Cube(10, 10, 10).debug()\n  background_object = Sphere(10).background()\n  disabled_object = Sphere(20).disable()\n  root_object = Sphere(5).root()\n\n\nVolumes\n---------\n\nWhile MuSCAD\'s ``Cube`` class mimics the OpenSCAD ``cube`` primitive, it is too simple to create useful objects.\nMuSCAD introduces a high-level class called ``Volume`` that offers extended possibilities over ``Cube``.\nFor a start, you don\'t have to define the size of a ``Volume``, you can specify its lower and upper limits on each axis instead::\n\n  from muscad import Volume\n\n  my_volume = Volume(left=2, right=10, back=10, front=20, bottom=-4, top=6)\n  print(my_volume)\n\n::\n\n  translate(v=[6.0, 15.0, 1.0]) \n  cube(size=[8, 10, 10], center=true);\n\nYou can also specify the size and one limit on a axis, or the center and the size, or the center and a limit.\nMuSCAD will extrapolate the rest::\n\n  my_volume = Volume(\n    left=2, width=8, # this defines the x axis\n    center_y=15, depth=10, # Y axis\n    center_z=1, top=6) # Z axis\n\n  print(my_volume)\n\nThis gives the same render as before::\n\n  translate(v=[6.0, 15.0, 1.0]) \n  cube(size=[8, 10.0, 10], center=true);\n\nNote that if you specify only one limit or a size for an axis, that axis will be centered on 0 by default.\n\nFillet & Chamfer\n=================\n\nYou can fillet or chamfer all edges of a ``Volume``::\n\n  chamfered_cube = (\n    Volume(width=10, depth=10, height=10)\n    .chamfer_all(1) #  apply a chamfer of radius 1 to all edges\n  )\n  # warning: this produces more than a hundred lines of OpenSCAD code\n  print(chamfered_cube)\n\nYou can fillet or chamfer specific edges of a Volume::\n\n  filleted_side_cube = (\n    Volume(width=10, depth=10, height=10)\n    .fillet_height(1, left=True) # this fillets the edges along the height of the Volume, restricted to edges on the left side\n    .fillet_depth(1, left=True) # this fillets the edges along the depth of the Volume, again restricted to edges on the left side\n  )\n  print(filleted_side_cube) # notice that the rendered cube is filleted only on its left side\n\nAs the name suggests, using ``.fillet_width()``, ``.fillet_depth()`` and ``.fillet_height()`` (and the\nmatching ``chamfer_*()`` methods) will cut your Volume\nalong its width, depth or height respectively. All 4 fours edges will be cut, unless you select specific edges using\nthe boolean parameters ``left``, ``right``, ``back``, ``front``, ``bottom`` and ``top``. By combining 2 of those, you\ncan select the specific edge to cut.\n\nReverse Fillet & Chamfer\n========================\n\nIn many situations, instead of cutting the edges of your Volumes, you want to add a fillet to soften an inner\nedge of a Volume with another part. That\'s a `reverse fillet` or `reverse chamfer`.\n\nUse the available ``reverse_fillet_<face>()`` methods to select the face where reverse fillets will be added,\nand if needed, select the sides with the boolean parameters ``left``, ``right``, ``back``, ``front``, ``bottom``\nand ``top``. Here is an example where a reverse fillet will be added at the back of the red part, to join it better\nwith the blue part: ::\n\n    blue_part = Volume(width=40, depth=12, height=6).fillet_height().color("blue")\n    red_part = (\n        Volume(\n            width=15,\n            back=blue_part.front,\n            depth=25,\n            center_z=blue_part.center_z,\n            height=blue_part.height,\n        )\n        .reverse_fillet_back(4, left=True, right=True)\n        .fillet_height(front=True)\n        .color("red")\n    )\n\n    print(blue_part + red_part)\n\nThis will create the following object:\n\n..    figure:: https://gitlab.com/guillp/muscad/-/raw/master/doc/images/reverse_fillet.png\n\n\n\nObject-Oriented Parts\n-----------------------\n\nNow that you are familiar with the basic syntax of primitives, boolean operations, transformations, and alignment, you might want to create complex objects.\nWhile this is possible with the basic functional syntax, it will soon create some hard-to-read code, and you might want some ways to better structure\nyou code. MuSCAD offers a ``Part`` class that you can inherit to define Objects in an actual `Object-Oriented` way :)\n\nHere is a simple example, with another tower of cubes, this time with 3 cubes::\n  \n  from muscad import Part, Cube\n\n  class CubeTower(Part):\n    def init(self):\n      self.add_child(\n        Cube(40, 40, 40).color(\'blue\')\n      )\n      self.add_child(\n        Cube(30, 30, 30).align(bottom=self.top).color(\'red\')\n      )\n      self.add_child(\n        Cube(20, 20, 20).align(bottom=self.top).color(\'yellow\')\n      )\n\n  print(CubeTower())\n\nThis gives the same OpenSCAD code as before::\n\n  union() {\n    color("blue") \n    cube(size=[40, 40, 40], center=true);\n    color("red") \n    translate(v=[0, 0, 35.0]) \n    cube(size=[30, 30, 30], center=true);\n    color("yellow") \n    translate(v=[0, 0, 65.0]) \n    cube(size=[20, 20, 20], center=true);\n  }\n\nBut this Python code doesn\'t look like much of an improvement, right? More code to do the same thing?\nThat\'s because it doesn\'t use the new possibilities offered by the ``Part`` class. Before introducing those features, let\'s understand what\'s going on::\n\n  class CubeTower(Part):\n\nThis creates a `CubeTower` class, which inherits MuSCAD ``Part`` class. Next::\n\n  def init(self):\n\nThis special method (not to be confused with Python constructor ``__init__()``) is the ``Part`` constructor. It will be executed whenever an object of this\nclass is instantiated. In this constructor::\n\n  self.add_child(\n    Cube(40, 40, 40).color(\'blue\')\n  )\n\nHere we create a blue ``Cube`` of size 40, and we add it as a child of this ``Part``. The object rendered by a part is made of the sum of its children.\nNotice that a ``Part`` is a MuSCAD object, so MuSCAD can always calculate its bounding box. That\'s what we use when we add the second and third Cubes::\n\n  self.add_child(\n    Cube(30, 30, 30).align(bottom=self.top).color(\'red\')\n  )\n  self.add_child(\n    Cube(20, 20, 20).align(bottom=self.top).color(\'yellow\')\n  )\n\nWe align that second ``Cube`` to the current Part top, which is, before the second Cube is added, the top of the first Cube. Once that second Cube is added,\nthat top "moves up" to include the second Cube, so the third Cube is added on top of it.\nSince Cubes are created centered by default, the 3 cubes center_x and center_y are aligned to 0.\n\nFinally, once we are done with the CubeTower class definition, we instantiate it and print the resulting OpenSCAD code::\n\n  print(CubeTower())\n\nNote that a instantiated Part is a MuSCAD ``Object``, so the bounding box can still be calculated::\n\n  tower = CubeTower()\n  assert tower.top == 70\n  assert tower.center_x = 0\n\nAnd the Part can be applied transformations or be used in boolean operations::\n\n  assert tower.align(top=0).top == 0\n  print(tower - Cylinder(d=2, h=tower.height))\n\nParametric parts\n==================\n\nThe ``init()`` method is executed when you instantiate a Part. You can add parameters to the ``init()`` method,\nwhich will then be required when instantiating the ``Part``::\n\n  class ParametricCubeTower(Part):\n    def init(self, biggest_size, narrowing=10, colors=(\'blue\', \'red\', \'yellow\', \'green\')):\n      size = biggest_size\n      for i, size in enumerate(range(biggest_size, 0, -narrowing)):\n        color = colors[i%len(colors)]\n        self.add_child(\n          Cube(size, size, size)\n          .color(color)\n          .align(bottom=self.bottom)\n        )\n\nNow we start to benefit from Python. The tower of cube will be made from a variable number of cubes depending on the arguments passed at instantiation.\nSo rendering the following code (using the ``ParametricCube`` class defined above)::\n\n   print(ParametricCubeTower(60))\n\nwill give::\n\n  union() {\n    color("blue") \n    cube(size=[60, 60, 60], center=true);\n    color("red") \n    cube(size=[50, 50, 50], center=true);\n    color("yellow") \n    cube(size=[40, 40, 40], center=true);\n    color("green") \n    cube(size=[30, 30, 30], center=true);\n    color("blue") \n    cube(size=[20, 20, 20], center=true);\n    color("red") \n    cube(size=[10, 10, 10], center=true);\n  }\n\nBut wait! There is more benefits from the ``Part`` class.\n\nNon parametrable children\n===========================\n\nDefining children in the ``init()`` method works well for parametrable children, but it is cumbersome when you have to deal with a lot of static,\nnon-parametrable children. Also, because they don\'t use any variable parameters, and because you have to call ``.add_child()`` everytime, you end with a lot\nof code noise. To avoid that, you can specify those children as class-level attributes instead.\nLet\'s rewrite our Cube Tower this way::\n\n    class StaticCubeTower(Part):\n      big_cube = Cube(40, 40, 40).color(\'blue\')\n      medium_cube = Cube(30, 30, 30).color(\'red\').align(bottom=big_cube.top)\n      small_cube = Cube(20, 20, 20).color(\'yellow\').align(bottom=medium_cube.top)\n\nSince you define class attributes, you have to give each a name. This has 2 additional benefits:\n\n  - you can use previously defined child attributes to align new ones. That\'s what we do when we align the ``medium_cube`` and ``small_cube`` on top of the ``big_cube`` and ``medium_cube`` respectively. That is more explicit than relying on the implicit current bounding box like we did before with ``self``.\n\n  - the attribute name will be part of the render, as comment, making it easier to find which MuSCAD code produces which OpenSCAD code.\n\nLet\'s check those comments::\n\n     print(StaticCubeTower())\n\ngives::\n\n   union() {\n      // big_cube\n      color("blue")\n      cube(size=[40, 40, 40], center=true);\n      // medium_cube\n      translate(v=[0, 0, 35.0])\n      color("red")\n      cube(size=[30, 30, 30], center=true);\n      // small_cube\n      translate(v=[0, 0, 60.0])\n      color("yellow")\n      cube(size=[20, 20, 20], center=true);\n    }\n\nSo you should model Parts by defining first the most constrained children, then designing other child around them.\n\nAll class-level attributes that are MuSCAD objects will be automatically added as children once an object of this class is instantiated,\nand before the ``init()`` method is executed. To avoid that, you can prefix the attribute name with an underscore. This will create a non-rendered child.\n\nNon-rendered children\n=======================\n\nYou can add some objects to a Part as a class attribute that will not be rendered, but that you can use them to align with some other children.\nTo do that, simply prefix its name by a _ like this ::\n\n  class MyPart(Part):\n    _nonrendered = Cube(10, 10, 10)\n    rendered = Sphere(10).align(bottom=_nonrendered.top)\n\n  print(MyPart())\n\nNote that any class-level attributes that are not ``Object``, ``Holes`` or ``Misc`` will not be part of the render. You can use them as class-level\nvariables.\n\nFirst class (unfillable) holes\n=================================\n\nOften, when defining an object, its "holes" are more important than its actual filled matter. You might needs those holes for screws, for extrusions, belts, etc.\nWhen you create such an hole, you don\'t want it to be accidentally filled again when adding more children later. To make things easier, a ``Part`` also has a list of holes, that are guaranteed to never be filled\nby another child. To add such an hole to a Part, simply call ``add_hole()`` with the object to remove as parameter.\nHere is an example, creating 2 crossed pipes::\n\n  class CrossedPipes(Part):\n    def init(self):\n      self.add_child(Cylinder(d=20, h=30))\n      self.add_hole(Cylinder(d=15, h=31))\n      self.add_child(Cylinder(d=20, h=30).x_rotate(90))\n      self.add_hole(Cylinder(d=15, h=31).x_rotate(90))\n\nFor this simple non-parametric example, you can also define holes as class-level attributes, by prefixing their definition by the operator ``~``::\n\n  class CrossedPipes(Part):\n    vertical_outer_pipe = Cylinder(d=20, h=30)\n    vertical_inner_pipe = ~Cylinder(d=15, h=31)\n    horizontal_outer_pipe = Cylinder(d=20, h=30).x_rotate(90)\n    horizontal_inner_pipe = ~Cylinder(d=15, h=31).x_rotate(90)\n\n\nMiscellaneous children\n======================\n\nHaving MuSCAD calculate automatically the dimensions of complex parts is great, but sometimes, there are some Parts that have some miscellaneous features,\nthat you don\'t want to be taken into consideration when aligning against other parts.\nFor example, a stepper motor has a shaft protuding on one of its side, but ignoring that shaft makes it easier to align the motor where you want it to be.\nTo create such a misc child, call ``add_misc()``. Here is very simplified Nema17 stepper motor, with the shaft on the top::\n\n  class StepperMotor(Part):\n    def init(self):\n      self.add_child(Cube(42, 42, 50)) # this is the body\n      self.add_misc(Cylinder(d=5, h=25).align(bottom=self.top)) # this is the shaft\n\nIt is also possible to declare misc children as class-attributes, by calling ``.misc()`` on the attribute value::\n\n  class StepperMotor(Part):\n    body = Cube(42, 42, 50)\n    shaft = Cylinder(d=5, h=25).align(bottom=body.top).misc() # this is a misc item\n\nLet\'s instantiate it and check its sizes on all axis::\n\n  stepper = StepperMotor()\n  assert stepper.width == 42\n  assert stepper.depth == 42\n  assert stepper.height == 50\n\nAs you can see, only the body (which is the only "real", as in "non-misc" child of that Part) determines the size of our simple stepper motor.\nAll misc objects are ignored.\n\nThis simple stepper is pretty much useless since it does not include details such as screws. Obviously, you will never want to\nprint a StepperMotor, you will use a real stepper that actually works instead. But you might want a stepper model anyway, that you can use\nwhen designing some parts, to align other parts against, or to use as a Hole in another part, such as a stepper attachment. If that stepper model would include all details\nsuch as screws, bulge, etc. , the fixation holes would be automatically created in whatever object you align on the fixing holes.\nLuckily, MuSCAD includes a much more detailed ``StepperMotor`` Part in its vitamins (see below). Let\'s use it to create a very simple\nstepper attachment::\n\n  from muscad.vitamins.steppers import StepperMotor\n\n  class StepperAttachment(Part):\n    stepper = ~StepperMotor() # let\'s create the stepper as a hole\n    attachment = Cube(80, stepper.width, 10).align(\n      center_x=stepper.center_x, center_y=stepper.center_y, bottom=stepper.top\n    ) # that\'s the simplest attachment you can imagine\n\nNotice that the StepperMotor is a hole since it is prefixed with the operator ``~``. Note that the order in which normal\nchildren and holes are declared does not matter for the rendering, it only matters to allow you\nto properly align those components together.\n\nLinear Extrusions on all axis\n-------------------------------\n\nTo ease the creation of surfaces that you can extrude, you can use the various methods provided by ``Surface``.\n\nWith OpenSCAD, you can only do a linear extrusion on the Z axis (the part "growing" upwards by a given distance). You then have to rotate and align the resulting part manually, which is quite cumbersome.\nWith MuSCAD, you can easily do linear extrusions on all 3 axis by using the methods ``.x_linear_extrude()``, ``.y_linear_extrude()``, and ``.z_linear_extrude()``. They will take care of rotating and aligning your part automatically.\n\nThose methods accept 4 parameters: an extrusion ``distance``, a start (``left``, ``back`` or ``bottom``), and end (``right``, ``front`` or ``top``) and a center (``center_x``, ``center_y`` or ``center_z``).\nYou only have to provide 2 of those, and MuSCAD will extrapolate the rest::\n\n    spade = (\n        Union(\n            Surface.free(\n                Circle(d=20).align(right=0, back=5),\n                Circle(d=2).align(center_x=0, front=35),\n            ),\n            Surface.free(\n                Circle(d=2).align(right=5, back=0),\n                Square(1, 15).align(left=0, back=0),\n            ),\n        )\n        .x_mirror(keep=True)\n        .y_linear_extrude(10, center_y=3)\n    )\n\n.. figure:: https://gitlab.com/guillp/muscad/-/raw/master/doc/images/spade_y.png\n\nVitamins\n-----------\nMuSCAD includes a library of models for parts commonly used in 3D printing: bolts and nuts, stepper motors, bearings, fans, pulleys, etc.\nYou can have a look at ``muscad.vitamins.*`` to use and import them.\nMost of those models are parametric, so you can have nuts and bolts of any size you like. When there are standardised sizes for some parts,\nyou can instantiate them via class level factories.\n\nCheck out the examples to see how they are used.\n\nExample Code\n--------------\n\nCheck out the examples `here <https://gitlab.com/guillp/muscad/tree/master/examples>`__.\nExamples include a 3D Printer model that I designed from scratch, inspired by the popular HyperCube Evolution, but with a Dual-Wire XY Gantry.\nThere are a few parts I designed to fix things around the house as well.\nFeel free to check it out, test it, improve it and share your findings!\n\nHere is a moderately complex example, demonstrating all the features exposed above. This is a feet for 3030 extrusion based printers such as the HyperCube Evolution::\n\n    from muscad import Part, Sphere, Volume\n    from muscad.vitamins.bolts import Bolt\n    from muscad.vitamins.brackets import CastBracket\n    from muscad.vitamins.extrusions import Extrusion\n\n\n    class Feet(Part):\n\n        # the next 3 parts are 3030 extrusion models, positionned on each axis, that represents\n        # the actual extrusions from the printer\n        # the feet parts will be aligned relatively to those extrusions\n        z_extrusion = ~Extrusion.e3030(60).background()\n        y_extrusion = (\n            ~Extrusion.e3030(60)\n            .bottom_to_front()\n            .align(\n                center_x=z_extrusion.center_x,\n                back=z_extrusion.front,\n                bottom=z_extrusion.bottom,\n            )\n            .background()\n        )\n        x_extrusion = (\n            ~Extrusion.e3030(60)\n            .bottom_to_right()\n            .align(\n                left=z_extrusion.right,\n                center_y=z_extrusion.center_y,\n                bottom=z_extrusion.bottom,\n            )\n            .background()\n        )\n\n        # a cast bracket to maintain the X and Y extrusion together\n        cast_bracket = (\n            ~CastBracket.bracket3030()\n            .align(\n                left=y_extrusion.right,\n                back=x_extrusion.front,\n                center_z=x_extrusion.center_z,\n            )\n            .debug()\n        )\n\n        # the base of the feet, that touches the X and Y extrusions\n        base = (\n            Volume(\n                left=z_extrusion.left,\n                right=cast_bracket.right,\n                back=z_extrusion.back,\n                front=cast_bracket.front,\n                top=z_extrusion.bottom,\n                height=6,\n            )\n            .fillet_height(10)\n            .fillet_height(30, right=True, front=True)\n        )\n\n        # bolts to attach the base to the extrusions\n        right_bolt = ~Bolt.M6(10).align(\n            center_x=base.right - 10,\n            center_y=x_extrusion.center_y,\n            center_z=x_extrusion.bottom - 2,\n        )\n        front_bolt = ~Bolt.M6(10).align(\n            center_x=y_extrusion.center_x,\n            center_y=base.front - 10,\n            center_z=y_extrusion.bottom - 2,\n        )\n        center_bolt = ~Bolt.M8(20).align(\n            center_x=z_extrusion.center_x,\n            center_y=z_extrusion.center_y,\n            center_z=z_extrusion.bottom - 2,\n        )\n\n        # let\'s make the feet height parametrable\n        def init(self, height=33):\n\n            self.ball_holder = (\n                Volume(\n                    left=self.z_extrusion.left,\n                    width=44,\n                    back=self.z_extrusion.back,\n                    depth=44,\n                    top=self.base.bottom,\n                    height=height,\n                )\n                .fillet_height(10)\n                .fillet_height(20, right=True, front=True)\n            )\n\n            # the actual squash ball, making a hole into the feet\n            self.squash_ball = (\n                ~Sphere(40)\n                .align(\n                    center_x=self.ball_holder.center_x,\n                    center_y=self.ball_holder.center_y,\n                    center_z=self.ball_holder.bottom + 1,\n                )\n                .debug()\n            )\n\n\n    if __name__ == "__main__":\n        Feet().render_to_file()\n\n\nThose less than 100 easy-to-read, easy-to-modify lines do render to about 200 hardly-readable, next-to-impossible to modify OpenSCAD lines.\nDon\'t try to modify them! MuSCAD is not designed to create OpenSCAD code that is easy to use for humans. It is intended to use only by OpenSCAD.\n\nThe rendered feet was parts of the sample images above.\n\nContact\n---------\n\nFeel free to play with MuSCAD.\nFor any issue, please open a ticket on GitLab. PR are welcome.\nGuillaume\n\nLicense\n--------\nMIT\n\nSome class docstrings are derived from the `OpenSCAD User Manual\n<https://en.wikibooks.org/wiki/OpenSCAD_User_Manual>`__, so\nare available under the `Creative Commons Attribution-ShareAlike License\n<https://creativecommons.org/licenses/by-sa/3.0/>`__. \n',
    'author': 'Guillaume Pujol',
    'author_email': 'guillp.linux@gmail.com',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://gitlab.com/guillp/muscad',
    'packages': packages,
    'package_data': package_data,
    'python_requires': '>=3.6,<4.0',
}


setup(**setup_kwargs)
