commit c91f2e4e836e5180e19a78a1b788e79b8081cdea Author: Deployment Bot (from Travis CI) Date: Tue Dec 6 19:14:45 2022 +0000 Deploy florianfesti/boxes to github.com/florianfesti/boxes.git:gh-pages diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/html/.buildinfo b/html/.buildinfo new file mode 100644 index 0000000..04ef36b --- /dev/null +++ b/html/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: d0b1451bbc4708a4c38e3dea4199c8c4 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/html/.nojekyll b/html/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/html/CONTRIBUTING.html b/html/CONTRIBUTING.html new file mode 100644 index 0000000..5bf5346 --- /dev/null +++ b/html/CONTRIBUTING.html @@ -0,0 +1,289 @@ + + + + + + + + + Contributing to Boxes.py — boxes.py 0.1 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Contributing to Boxes.py

+

You are thinking about contributing to Boxes.py? That’s great! +Boxes.py is designed to be re-used and extended.

+

This document gives you some guidelines how your contribution is most +likely to impact the development and your changes are most likely to +be merged into the upstream repository.

+

Most of them should be just general best practises and not be +surprising. Don’t worry if you find them too complicated. It is OK +leave the final touch to someone else.

+
+

Writing code for Boxes.py

+

You will often be compelled to just do a quick thing that will solve +your immediate needs. That’s fine. But nevertheless it is often worth +doing things the right way and be able to submit your changes +upstream. For one to give something back to the community. But also +for purely selfish reasons like getting the code maintained. Also +Boxes.py is designed to make doing things properly the easy way.

+

Here are some guidelines that make this easier. Depending on what you +are up to they may apply to a varying degree. It’s ok to submit +patches that are not quite ready yet. But please state in the pull +request message what you think the status is and whether you want help +or are going to finish it on your own.

+
    +
  • Please fork the repository at GitHub before getting started

  • +
  • Start with creating separate branches for each of your new generators or features

    +
      +
    • You can merge them into your master branch to have them all in one place

    • +
    • Please continue your work in the branches and repeatedly merge them to master

    • +
    +
  • +
  • Before submitting a pull request intended to go upstream have clean patches that are self contained and error free

    +
      +
    • Re-order and squash patches with git rebase -i

    • +
    • The patches should containing meaningful changes and not (necessarily) reflect how the code was created

    • +
    • Rebase your branch to the current master branch

    • +
    • Be prepared that your code may get reworked before being merged upstream

    • +
    +
  • +
  • Submit a pull request in GitHub based on your feature branch

    +
      +
    • Describe the status of the patch set and your intentions with it in the pull request message

    • +
    +
  • +
+

If you want to discuss your idea open a ticket describing it and ask +questions there. This is encouraged even if you think you know what +you want to do. There are many short cuts in Boxes.py and pointing you +in the right direction may save you a lot of work.

+

If you want feed back on you code feel free to open a PR. State that +this is work in progress in the PR message. It’s OK if it does not +follow the guidelines (yet).

+
+

Writing new Generators

+

Writing new generators is the most straight forward thing to do with +Boxes.py. Here are some guidelines that make it easier to get them added:

+
    +
  • Start with a copy of another generator or boxes/generators/_template.py

  • +
  • Commit changes to the library in separate patches

  • +
  • Use parameters with sane defaults instead of hard coding dimensions

  • +
  • Simple generators can end up as one single commit

  • +
  • For more complicated generators there can be multiple patches - +each adding another feature

  • +
+
+
+

Adding new Dependencies

+

Adding new dependencies should be considered thoroughly. If a new +depencendcy is added it needs to be added in all these places:

+
    +
  • documentation/src/install.rst

  • +
  • RST files in documentation/src/install/

  • +
  • scripts/Dockerfile

  • +
  • .travis.yml

  • +
+

If it is a Python module it also needs to be added: +* requirements.txt +* setup.py

+
+
+
+

Improving the Documentation

+

Boxes.py comes with Sphinx based documentation that is in large parts +generated from the doc strings in the code. Nevertheless documentation +has a tendency to get outdated. If you encounter outdated pieces of +documentation feel free to submit a pull request or open a ticket +pointing out what should be changed or even suggesting a better text.

+

To check your changes docs need to be build with make html in +documentation/src. This places the compiled documentation in +documentation/build/html. You need to have sphinx installed for +this to work.

+

The online documentation gets build and updated automatically by the Travis CI +as soon as the changes makes it into the GitHub master branch.

+
+
+

Provide photos for generators

+

Many generators still come without an example photo. If you are +creating such an item consider donating a good picture. You can +simply attach it to ticket #140. If you want you can +also create a proper pull request instead:

+
    +
  • Make sure you have sh, convert (ImageMagick), sed and sha256sum installed

  • +
  • The picture needs to be an jpg file with the name of the generator +(This is case sensitive. Use CamelCase.)

  • +
  • The picture should be 1200 pixels wide and square or not too far +from square (3:4 is fine).

  • +
  • Place the file in static/samples/

  • +
  • Check if the picture shows up at the bottom of the settings page of +the generator when running scripts/boxesserver

  • +
  • Change dir to ./scripts and there execute ./gen_thumbnails.sh

  • +
  • Check if the thumbnail is seen in the main page when hovering over +the generator entry

  • +
  • Create a commit including static/samples/$GeneratorName*.jpg and +static/samples/samples.sha256

  • +
  • Create a pull request from that

  • +
+
+
+

Improving the User Interface

+

Coming up with good names and good descriptions is hard. Often writing +a new generator is much easier than coming up with a good name for it +and its arguments. If you think something deserves a better name or +description and you can come up with one please don’t hesitate to open +a ticket. It is this small things that make something like Boxes.py +easy or hard to use.

+

There is also an - often empty - space for a longer text for each +generator that could house assembling instructions, instructions for +use or just more detailed descriptions. If you are interested in +writing some please open a ticket. Your text does not have to be +perfect. We can work on it together.

+
+
+

Running the Code

+

To serve website, run scripts/boxesserver script

+
+
+

Reporting bugs

+

If you encounter issues with Boxes.py, please open a ticket at +GitHub. Please provide all information necessary to reproduce the +bug. Often this can be the URL of the broken result. If the issue is +easy to spot it may be sufficient to just give a brief +description. Otherwise it can be helpful to attach the resulting SVG, +a screen shot or the error message. Add a “bug” tag to draw additional +attention.

+
+
+

Suggesting new generators or features

+

If you have an idea for a new generator or feature please open a +ticket. Give some short rational how or where you would use such a +thing. Try to give a precise description how it should look like and +which features and details are important. The less is left open the +easier it is to implement. You can add an “enhancement” tag.

+
+
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/README.html b/html/README.html new file mode 100644 index 0000000..f93b5f9 --- /dev/null +++ b/html/README.html @@ -0,0 +1,205 @@ + + + + + + + + + About Boxes.py — boxes.py 0.1 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

About Boxes.py

+ + + + + + + + + +
_images/NotesHolder.jpg +_images/OttoBody.jpg +_images/PaintStorage.jpg +_images/ShutterBox.jpg +_images/TwoPiece.jpg +
+
    +
  • Boxes.py is an online box generator

    + +
  • +
  • Boxes.py is an Inkscape plug-in

  • +
  • Boxes.py is library to write your own

  • +
  • Boxes.py is free software licensed under GPL v3+

  • +
  • Boxes.py is written in Python and runs with Python 3

  • +
+

Boxes.py comes with a growing set of ready-to-use, fully parametrized +generators. See https://florianfesti.github.io/boxes/html/generators.html for the full list.

+ + + + + + + +
_images/AngledBox.jpg +_images/FlexBox2.jpg +_images/HingeBox.jpg +
+
+

Features

+

Boxes.py generates SVG images that can be viewed directly in a web browser but also +postscript and - with pstoedit as external helper - other vector formats +including dxf, plt (aka hpgl) and gcode.

+

Of course the library and the generators allow selecting the “thickness” +of the material used and automatically adjusts lengths and width of +joining fingers and other elements.

+

The “burn” parameter compensates for the material removed by the laser. This +allows fine tuning the gaps between joins up to the point where plywood +can be press fitted even without any glue.

+

Finger Joints are the work horse of the library. They allow 90° edges +and T connections. Their size is scaled up with the material +“thickness” to maintain the same appearance. The library also allows +putting holes and slots for screws (bed bolts) into finger joints, +although this is currently not supported for the included generators.

+

Dovetail joints can be used to join pieces in the same plane.

+

Flex cuts allows bending and stretching the material in one direction. This +is used for rounded edges and living hinges.

+ + + + + + + + + + + +
_images/TypeTray.jpg +_images/BinTray.jpg +_images/DisplayShelf.jpg +
_images/AgricolaInsert.jpg +_images/HeartBox.jpg +_images/Atreus21.jpg +
+
+
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_images/AgricolaInsert.jpg b/html/_images/AgricolaInsert.jpg new file mode 100644 index 0000000..ac33c7b Binary files /dev/null and b/html/_images/AgricolaInsert.jpg differ diff --git a/html/_images/AgricolaInsert1.jpg b/html/_images/AgricolaInsert1.jpg new file mode 100644 index 0000000..ac33c7b Binary files /dev/null and b/html/_images/AgricolaInsert1.jpg differ diff --git a/html/_images/AllEdges.jpg b/html/_images/AllEdges.jpg new file mode 100644 index 0000000..f298496 Binary files /dev/null and b/html/_images/AllEdges.jpg differ diff --git a/html/_images/AngledBox.jpg b/html/_images/AngledBox.jpg new file mode 100644 index 0000000..2276773 Binary files /dev/null and b/html/_images/AngledBox.jpg differ diff --git a/html/_images/AngledBox1.jpg b/html/_images/AngledBox1.jpg new file mode 100644 index 0000000..2276773 Binary files /dev/null and b/html/_images/AngledBox1.jpg differ diff --git a/html/_images/AngledCutJig.jpg b/html/_images/AngledCutJig.jpg new file mode 100644 index 0000000..a9712f2 Binary files /dev/null and b/html/_images/AngledCutJig.jpg differ diff --git a/html/_images/Atreus21.jpg b/html/_images/Atreus21.jpg new file mode 100644 index 0000000..42eefe9 Binary files /dev/null and b/html/_images/Atreus21.jpg differ diff --git a/html/_images/Atreus211.jpg b/html/_images/Atreus211.jpg new file mode 100644 index 0000000..42eefe9 Binary files /dev/null and b/html/_images/Atreus211.jpg differ diff --git a/html/_images/BasedBox.jpg b/html/_images/BasedBox.jpg new file mode 100644 index 0000000..4aca630 Binary files /dev/null and b/html/_images/BasedBox.jpg differ diff --git a/html/_images/BayonetBox.jpg b/html/_images/BayonetBox.jpg new file mode 100644 index 0000000..14cc40a Binary files /dev/null and b/html/_images/BayonetBox.jpg differ diff --git a/html/_images/BinTray.jpg b/html/_images/BinTray.jpg new file mode 100644 index 0000000..6a49e39 Binary files /dev/null and b/html/_images/BinTray.jpg differ diff --git a/html/_images/BinTray1.jpg b/html/_images/BinTray1.jpg new file mode 100644 index 0000000..6a49e39 Binary files /dev/null and b/html/_images/BinTray1.jpg differ diff --git a/html/_images/BottleTag.jpg b/html/_images/BottleTag.jpg new file mode 100644 index 0000000..4810ab2 Binary files /dev/null and b/html/_images/BottleTag.jpg differ diff --git a/html/_images/BurnTest.jpg b/html/_images/BurnTest.jpg new file mode 100644 index 0000000..ad1ded9 Binary files /dev/null and b/html/_images/BurnTest.jpg differ diff --git a/html/_images/CanStorage.jpg b/html/_images/CanStorage.jpg new file mode 100644 index 0000000..3a2cd6b Binary files /dev/null and b/html/_images/CanStorage.jpg differ diff --git a/html/_images/CardBox.jpg b/html/_images/CardBox.jpg new file mode 100644 index 0000000..6f96ca5 Binary files /dev/null and b/html/_images/CardBox.jpg differ diff --git a/html/_images/CardHolder.jpg b/html/_images/CardHolder.jpg new file mode 100644 index 0000000..d47cdd2 Binary files /dev/null and b/html/_images/CardHolder.jpg differ diff --git a/html/_images/Castle.jpg b/html/_images/Castle.jpg new file mode 100644 index 0000000..e176f7c Binary files /dev/null and b/html/_images/Castle.jpg differ diff --git a/html/_images/ClosedBox.jpg b/html/_images/ClosedBox.jpg new file mode 100644 index 0000000..5891fe6 Binary files /dev/null and b/html/_images/ClosedBox.jpg differ diff --git a/html/_images/CoffeeCapsuleHolder.jpg b/html/_images/CoffeeCapsuleHolder.jpg new file mode 100644 index 0000000..c33dfdb Binary files /dev/null and b/html/_images/CoffeeCapsuleHolder.jpg differ diff --git a/html/_images/CoinDisplay.jpg b/html/_images/CoinDisplay.jpg new file mode 100644 index 0000000..8d36c14 Binary files /dev/null and b/html/_images/CoinDisplay.jpg differ diff --git a/html/_images/Console.jpg b/html/_images/Console.jpg new file mode 100644 index 0000000..1ccec4f Binary files /dev/null and b/html/_images/Console.jpg differ diff --git a/html/_images/Console2.jpg b/html/_images/Console2.jpg new file mode 100644 index 0000000..7822242 Binary files /dev/null and b/html/_images/Console2.jpg differ diff --git a/html/_images/DiceBox.jpg b/html/_images/DiceBox.jpg new file mode 100644 index 0000000..27e6bce Binary files /dev/null and b/html/_images/DiceBox.jpg differ diff --git a/html/_images/DiscRack.jpg b/html/_images/DiscRack.jpg new file mode 100644 index 0000000..a8341eb Binary files /dev/null and b/html/_images/DiscRack.jpg differ diff --git a/html/_images/DisplayCase.jpg b/html/_images/DisplayCase.jpg new file mode 100644 index 0000000..c642c8f Binary files /dev/null and b/html/_images/DisplayCase.jpg differ diff --git a/html/_images/DisplayShelf.jpg b/html/_images/DisplayShelf.jpg new file mode 100644 index 0000000..89f1186 Binary files /dev/null and b/html/_images/DisplayShelf.jpg differ diff --git a/html/_images/DisplayShelf1.jpg b/html/_images/DisplayShelf1.jpg new file mode 100644 index 0000000..89f1186 Binary files /dev/null and b/html/_images/DisplayShelf1.jpg differ diff --git a/html/_images/DividerTray.jpg b/html/_images/DividerTray.jpg new file mode 100644 index 0000000..abbfe58 Binary files /dev/null and b/html/_images/DividerTray.jpg differ diff --git a/html/_images/DoubleFlexDoorBox.jpg b/html/_images/DoubleFlexDoorBox.jpg new file mode 100644 index 0000000..0dcb9a5 Binary files /dev/null and b/html/_images/DoubleFlexDoorBox.jpg differ diff --git a/html/_images/DrillStand.jpg b/html/_images/DrillStand.jpg new file mode 100644 index 0000000..ca3de11 Binary files /dev/null and b/html/_images/DrillStand.jpg differ diff --git a/html/_images/ElectronicsBox.jpg b/html/_images/ElectronicsBox.jpg new file mode 100644 index 0000000..9d3005b Binary files /dev/null and b/html/_images/ElectronicsBox.jpg differ diff --git a/html/_images/EuroRackSkiff.jpg b/html/_images/EuroRackSkiff.jpg new file mode 100644 index 0000000..ca1236a Binary files /dev/null and b/html/_images/EuroRackSkiff.jpg differ diff --git a/html/_images/FlexBox.jpg b/html/_images/FlexBox.jpg new file mode 100644 index 0000000..c26271a Binary files /dev/null and b/html/_images/FlexBox.jpg differ diff --git a/html/_images/FlexBox2.jpg b/html/_images/FlexBox2.jpg new file mode 100644 index 0000000..44ce889 Binary files /dev/null and b/html/_images/FlexBox2.jpg differ diff --git a/html/_images/FlexBox21.jpg b/html/_images/FlexBox21.jpg new file mode 100644 index 0000000..44ce889 Binary files /dev/null and b/html/_images/FlexBox21.jpg differ diff --git a/html/_images/FlexBox3.jpg b/html/_images/FlexBox3.jpg new file mode 100644 index 0000000..97b28f6 Binary files /dev/null and b/html/_images/FlexBox3.jpg differ diff --git a/html/_images/FlexBox4.jpg b/html/_images/FlexBox4.jpg new file mode 100644 index 0000000..92b66c5 Binary files /dev/null and b/html/_images/FlexBox4.jpg differ diff --git a/html/_images/FlexTest.jpg b/html/_images/FlexTest.jpg new file mode 100644 index 0000000..3bd1949 Binary files /dev/null and b/html/_images/FlexTest.jpg differ diff --git a/html/_images/Folder.jpg b/html/_images/Folder.jpg new file mode 100644 index 0000000..9d678e6 Binary files /dev/null and b/html/_images/Folder.jpg differ diff --git a/html/_images/HalfBox.jpg b/html/_images/HalfBox.jpg new file mode 100644 index 0000000..05183dd Binary files /dev/null and b/html/_images/HalfBox.jpg differ diff --git a/html/_images/HeartBox.jpg b/html/_images/HeartBox.jpg new file mode 100644 index 0000000..1740d10 Binary files /dev/null and b/html/_images/HeartBox.jpg differ diff --git a/html/_images/HeartBox1.jpg b/html/_images/HeartBox1.jpg new file mode 100644 index 0000000..1740d10 Binary files /dev/null and b/html/_images/HeartBox1.jpg differ diff --git a/html/_images/HingeBox.jpg b/html/_images/HingeBox.jpg new file mode 100644 index 0000000..2468e47 Binary files /dev/null and b/html/_images/HingeBox.jpg differ diff --git a/html/_images/HingeBox1.jpg b/html/_images/HingeBox1.jpg new file mode 100644 index 0000000..2468e47 Binary files /dev/null and b/html/_images/HingeBox1.jpg differ diff --git a/html/_images/IntegratedHingeBox.jpg b/html/_images/IntegratedHingeBox.jpg new file mode 100644 index 0000000..c7785f1 Binary files /dev/null and b/html/_images/IntegratedHingeBox.jpg differ diff --git a/html/_images/JointPanel.jpg b/html/_images/JointPanel.jpg new file mode 100644 index 0000000..e808e10 Binary files /dev/null and b/html/_images/JointPanel.jpg differ diff --git a/html/_images/LBeam.jpg b/html/_images/LBeam.jpg new file mode 100644 index 0000000..7ada9db Binary files /dev/null and b/html/_images/LBeam.jpg differ diff --git a/html/_images/LaserClamp.jpg b/html/_images/LaserClamp.jpg new file mode 100644 index 0000000..760e2db Binary files /dev/null and b/html/_images/LaserClamp.jpg differ diff --git a/html/_images/LaserHoldfast.jpg b/html/_images/LaserHoldfast.jpg new file mode 100644 index 0000000..a64b4fc Binary files /dev/null and b/html/_images/LaserHoldfast.jpg differ diff --git a/html/_images/MagazinFile.jpg b/html/_images/MagazinFile.jpg new file mode 100644 index 0000000..ec7942c Binary files /dev/null and b/html/_images/MagazinFile.jpg differ diff --git a/html/_images/MakitaPowerSupply.jpg b/html/_images/MakitaPowerSupply.jpg new file mode 100644 index 0000000..fe3645c Binary files /dev/null and b/html/_images/MakitaPowerSupply.jpg differ diff --git a/html/_images/NotesHolder.jpg b/html/_images/NotesHolder.jpg new file mode 100644 index 0000000..27e30e6 Binary files /dev/null and b/html/_images/NotesHolder.jpg differ diff --git a/html/_images/NotesHolder1.jpg b/html/_images/NotesHolder1.jpg new file mode 100644 index 0000000..27e30e6 Binary files /dev/null and b/html/_images/NotesHolder1.jpg differ diff --git a/html/_images/OpenBox.jpg b/html/_images/OpenBox.jpg new file mode 100644 index 0000000..05c16b2 Binary files /dev/null and b/html/_images/OpenBox.jpg differ diff --git a/html/_images/OttoBody.jpg b/html/_images/OttoBody.jpg new file mode 100644 index 0000000..17c783d Binary files /dev/null and b/html/_images/OttoBody.jpg differ diff --git a/html/_images/OttoBody1.jpg b/html/_images/OttoBody1.jpg new file mode 100644 index 0000000..17c783d Binary files /dev/null and b/html/_images/OttoBody1.jpg differ diff --git a/html/_images/PaintStorage.jpg b/html/_images/PaintStorage.jpg new file mode 100644 index 0000000..539dfe2 Binary files /dev/null and b/html/_images/PaintStorage.jpg differ diff --git a/html/_images/PaintStorage1.jpg b/html/_images/PaintStorage1.jpg new file mode 100644 index 0000000..539dfe2 Binary files /dev/null and b/html/_images/PaintStorage1.jpg differ diff --git a/html/_images/PaperBox.jpg b/html/_images/PaperBox.jpg new file mode 100644 index 0000000..362c27e Binary files /dev/null and b/html/_images/PaperBox.jpg differ diff --git a/html/_images/PhoneHolder.jpg b/html/_images/PhoneHolder.jpg new file mode 100644 index 0000000..f5643fc Binary files /dev/null and b/html/_images/PhoneHolder.jpg differ diff --git a/html/_images/Platonic.jpg b/html/_images/Platonic.jpg new file mode 100644 index 0000000..f6f548d Binary files /dev/null and b/html/_images/Platonic.jpg differ diff --git a/html/_images/RegularBox.jpg b/html/_images/RegularBox.jpg new file mode 100644 index 0000000..bacf78b Binary files /dev/null and b/html/_images/RegularBox.jpg differ diff --git a/html/_images/RegularStarBox.jpg b/html/_images/RegularStarBox.jpg new file mode 100644 index 0000000..e158bb2 Binary files /dev/null and b/html/_images/RegularStarBox.jpg differ diff --git a/html/_images/RobotArm.jpg b/html/_images/RobotArm.jpg new file mode 100644 index 0000000..63b572a Binary files /dev/null and b/html/_images/RobotArm.jpg differ diff --git a/html/_images/Rotary.jpg b/html/_images/Rotary.jpg new file mode 100644 index 0000000..2a100b6 Binary files /dev/null and b/html/_images/Rotary.jpg differ diff --git a/html/_images/RoundedBox.jpg b/html/_images/RoundedBox.jpg new file mode 100644 index 0000000..42a3234 Binary files /dev/null and b/html/_images/RoundedBox.jpg differ diff --git a/html/_images/RoyalGame.jpg b/html/_images/RoyalGame.jpg new file mode 100644 index 0000000..36161f4 Binary files /dev/null and b/html/_images/RoyalGame.jpg differ diff --git a/html/_images/SBCMicroRack.jpg b/html/_images/SBCMicroRack.jpg new file mode 100644 index 0000000..97d8e4b Binary files /dev/null and b/html/_images/SBCMicroRack.jpg differ diff --git a/html/_images/ShutterBox.jpg b/html/_images/ShutterBox.jpg new file mode 100644 index 0000000..78d517e Binary files /dev/null and b/html/_images/ShutterBox.jpg differ diff --git a/html/_images/ShutterBox1.jpg b/html/_images/ShutterBox1.jpg new file mode 100644 index 0000000..78d517e Binary files /dev/null and b/html/_images/ShutterBox1.jpg differ diff --git a/html/_images/Silverware.jpg b/html/_images/Silverware.jpg new file mode 100644 index 0000000..823bf41 Binary files /dev/null and b/html/_images/Silverware.jpg differ diff --git a/html/_images/SlidingDrawer.jpg b/html/_images/SlidingDrawer.jpg new file mode 100644 index 0000000..ac06d03 Binary files /dev/null and b/html/_images/SlidingDrawer.jpg differ diff --git a/html/_images/SpicesRack.jpg b/html/_images/SpicesRack.jpg new file mode 100644 index 0000000..cf22f9d Binary files /dev/null and b/html/_images/SpicesRack.jpg differ diff --git a/html/_images/Stachel.jpg b/html/_images/Stachel.jpg new file mode 100644 index 0000000..756cf79 Binary files /dev/null and b/html/_images/Stachel.jpg differ diff --git a/html/_images/StorageRack.jpg b/html/_images/StorageRack.jpg new file mode 100644 index 0000000..21d18d7 Binary files /dev/null and b/html/_images/StorageRack.jpg differ diff --git a/html/_images/StorageShelf.jpg b/html/_images/StorageShelf.jpg new file mode 100644 index 0000000..5d1a6b0 Binary files /dev/null and b/html/_images/StorageShelf.jpg differ diff --git a/html/_images/TrafficLight.jpg b/html/_images/TrafficLight.jpg new file mode 100644 index 0000000..a508ede Binary files /dev/null and b/html/_images/TrafficLight.jpg differ diff --git a/html/_images/TrayInsert.jpg b/html/_images/TrayInsert.jpg new file mode 100644 index 0000000..4c7c096 Binary files /dev/null and b/html/_images/TrayInsert.jpg differ diff --git a/html/_images/TrayLayout.jpg b/html/_images/TrayLayout.jpg new file mode 100644 index 0000000..5b996ae Binary files /dev/null and b/html/_images/TrayLayout.jpg differ diff --git a/html/_images/TrayLayout2.jpg b/html/_images/TrayLayout2.jpg new file mode 100644 index 0000000..5b996ae Binary files /dev/null and b/html/_images/TrayLayout2.jpg differ diff --git a/html/_images/TwoPiece.jpg b/html/_images/TwoPiece.jpg new file mode 100644 index 0000000..265cf29 Binary files /dev/null and b/html/_images/TwoPiece.jpg differ diff --git a/html/_images/TwoPiece1.jpg b/html/_images/TwoPiece1.jpg new file mode 100644 index 0000000..265cf29 Binary files /dev/null and b/html/_images/TwoPiece1.jpg differ diff --git a/html/_images/TypeTray.jpg b/html/_images/TypeTray.jpg new file mode 100644 index 0000000..b4adbdc Binary files /dev/null and b/html/_images/TypeTray.jpg differ diff --git a/html/_images/TypeTray1.jpg b/html/_images/TypeTray1.jpg new file mode 100644 index 0000000..b4adbdc Binary files /dev/null and b/html/_images/TypeTray1.jpg differ diff --git a/html/_images/UBox.jpg b/html/_images/UBox.jpg new file mode 100644 index 0000000..591664c Binary files /dev/null and b/html/_images/UBox.jpg differ diff --git a/html/_images/UnevenHeightBox.jpg b/html/_images/UnevenHeightBox.jpg new file mode 100644 index 0000000..11e055e Binary files /dev/null and b/html/_images/UnevenHeightBox.jpg differ diff --git a/html/_images/UniversalBox.jpg b/html/_images/UniversalBox.jpg new file mode 100644 index 0000000..4a90814 Binary files /dev/null and b/html/_images/UniversalBox.jpg differ diff --git a/html/_images/WallCaliper.jpg b/html/_images/WallCaliper.jpg new file mode 100644 index 0000000..304eed2 Binary files /dev/null and b/html/_images/WallCaliper.jpg differ diff --git a/html/_images/WallDrillBox.jpg b/html/_images/WallDrillBox.jpg new file mode 100644 index 0000000..7784fc7 Binary files /dev/null and b/html/_images/WallDrillBox.jpg differ diff --git a/html/_images/WallPliersHolder.jpg b/html/_images/WallPliersHolder.jpg new file mode 100644 index 0000000..bd32ba1 Binary files /dev/null and b/html/_images/WallPliersHolder.jpg differ diff --git a/html/_images/WallSlottedHolder.jpg b/html/_images/WallSlottedHolder.jpg new file mode 100644 index 0000000..440f076 Binary files /dev/null and b/html/_images/WallSlottedHolder.jpg differ diff --git a/html/_images/WallTypeTray.jpg b/html/_images/WallTypeTray.jpg new file mode 100644 index 0000000..288fc51 Binary files /dev/null and b/html/_images/WallTypeTray.jpg differ diff --git a/html/_images/WineRack.jpg b/html/_images/WineRack.jpg new file mode 100644 index 0000000..d56c9a5 Binary files /dev/null and b/html/_images/WineRack.jpg differ diff --git a/html/_images/burn.svg b/html/_images/burn.svg new file mode 100644 index 0000000..44a3633 --- /dev/null +++ b/html/_images/burn.svg @@ -0,0 +1,61 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/html/_images/burn2.svg b/html/_images/burn2.svg new file mode 100644 index 0000000..ed1298c --- /dev/null +++ b/html/_images/burn2.svg @@ -0,0 +1,99 @@ + + + + + + RoundedBox + + + + FlexBox - RoundedBox + 2021-04-25 18:47:19 + http://localhost:8000/RoundedBox?FingerJoint_angle=90.0&FingerJoint_style=rectangular&FingerJoint_surroundingspaces=1&FingerJoint_edge_width=1.0&FingerJoint_finger=2.0&FingerJoint_play=0.0&FingerJoint_space=2.0&FingerJoint_width=1.0&Flex_stretch=1.05&Flex_connection=1.0&Flex_distance=0.5&Flex_width=5.0&x=50&y=50&h=50&outside=0&radius=5&wallpieces=1&edge_style=f&top=closed&thickness=3.0&format=svg&tabs=0.0&debug=0&reference=100&burn=1&render=1 + Box with vertical edges rounded + +Created with Boxes.py (https://festi.info/boxes.py) +Command line: boxes RoundedBox --FingerJoint_angle=90.0 --FingerJoint_style=rectangular --FingerJoint_surroundingspaces=1 --FingerJoint_edge_width=1.0 --FingerJoint_finger=2.0 --FingerJoint_play=0.0 --FingerJoint_space=2.0 --FingerJoint_width=1.0 --Flex_stretch=1.05 --Flex_connection=1.0 --Flex_distance=0.5 --Flex_width=5.0 --x=50 --y=50 --h=50 --outside=0 --radius=5 --wallpieces=1 --edge_style=f --top=closed --thickness=3.0 --format=svg --tabs=0.0 --debug=0 --reference=100 --burn=1 +Url: http://localhost:8000/RoundedBox?FingerJoint_angle=90.0&FingerJoint_style=rectangular&FingerJoint_surroundingspaces=1&FingerJoint_edge_width=1.0&FingerJoint_finger=2.0&FingerJoint_play=0.0&FingerJoint_space=2.0&FingerJoint_width=1.0&Flex_stretch=1.05&Flex_connection=1.0&Flex_distance=0.5&Flex_width=5.0&x=50&y=50&h=50&outside=0&radius=5&wallpieces=1&edge_style=f&top=closed&thickness=3.0&format=svg&tabs=0.0&debug=0&reference=100&burn=1&render=1 +SettingsUrl: http://localhost:8000/RoundedBox?FingerJoint_angle=90.0&FingerJoint_style=rectangular&FingerJoint_surroundingspaces=1&FingerJoint_edge_width=1.0&FingerJoint_finger=2.0&FingerJoint_play=0.0&FingerJoint_space=2.0&FingerJoint_width=1.0&Flex_stretch=1.05&Flex_connection=1.0&Flex_distance=0.5&Flex_width=5.0&x=50&y=50&h=50&outside=0&radius=5&wallpieces=1&edge_style=f&top=closed&thickness=3.0&format=svg&tabs=0.0&debug=0&reference=100&burn=1 + + image/svg+xml + + + + + + + + + + + + diff --git a/html/_images/overcuts.svg b/html/_images/overcuts.svg new file mode 100644 index 0000000..39e6a8e --- /dev/null +++ b/html/_images/overcuts.svg @@ -0,0 +1,62 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/html/_images/win11-wsl-boxesserver-localhost.png b/html/_images/win11-wsl-boxesserver-localhost.png new file mode 100644 index 0000000..83c00a6 Binary files /dev/null and b/html/_images/win11-wsl-boxesserver-localhost.png differ diff --git a/html/_images/windows_boxespy_zip_extract.png b/html/_images/windows_boxespy_zip_extract.png new file mode 100644 index 0000000..2c45312 Binary files /dev/null and b/html/_images/windows_boxespy_zip_extract.png differ diff --git a/html/_images/windows_browser_boxespy.png b/html/_images/windows_browser_boxespy.png new file mode 100644 index 0000000..5adeed8 Binary files /dev/null and b/html/_images/windows_browser_boxespy.png differ diff --git a/html/_images/windows_browser_download_boxespy.png b/html/_images/windows_browser_download_boxespy.png new file mode 100644 index 0000000..0fb9578 Binary files /dev/null and b/html/_images/windows_browser_download_boxespy.png differ diff --git a/html/_images/windows_browser_download_python.png b/html/_images/windows_browser_download_python.png new file mode 100644 index 0000000..06b14a1 Binary files /dev/null and b/html/_images/windows_browser_download_python.png differ diff --git a/html/_images/windows_cmd_python_boxesserver_firewall.png b/html/_images/windows_cmd_python_boxesserver_firewall.png new file mode 100644 index 0000000..b3f8e76 Binary files /dev/null and b/html/_images/windows_cmd_python_boxesserver_firewall.png differ diff --git a/html/_images/windows_install_python_path.png b/html/_images/windows_install_python_path.png new file mode 100644 index 0000000..014257a Binary files /dev/null and b/html/_images/windows_install_python_path.png differ diff --git a/html/_modules/boxes.html b/html/_modules/boxes.html new file mode 100644 index 0000000..91c87e1 --- /dev/null +++ b/html/_modules/boxes.html @@ -0,0 +1,2996 @@ + + + + + + + + boxes — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import math
+import sys
+import argparse
+from argparse import ArgumentParser
+import re
+from functools import wraps
+from xml.sax.saxutils import quoteattr
+from contextlib import contextmanager
+import copy
+from shlex import quote
+from shapely.geometry import *
+from shapely.ops import split
+import random
+
+from boxes import edges
+from boxes import formats
+from boxes import svgutil
+from boxes import gears
+from boxes import pulley
+from boxes import parts
+from boxes.Color  import *
+
+### Helpers
+
+
[docs]def dist(dx, dy): + """ + Return distance + + :param dx: delta x + :param dy: delat y + """ + return (dx * dx + dy * dy) ** 0.5
+ +
[docs]def restore(func): + """ + Wrapper: Restore coordiantes after function + + :param func: function to wrap + + """ + + @wraps(func) + def f(self, *args, **kw): + with self.saved_context(): + pt = self.ctx.get_current_point() + func(self, *args, **kw) + self.ctx.move_to(*pt) + + return f
+ + +
[docs]def holeCol(func): + """ + Wrapper: color holes differently + + :param func: function to wrap + + """ + + @wraps(func) + def f(self, *args, **kw): + if "color" in kw: + color = kw.pop("color") + else: + color = Color.INNER_CUT + + self.ctx.stroke() + with self.saved_context(): + self.set_source_color(color) + func(self, *args, **kw) + self.ctx.stroke() + + return f
+ + +############################################################################# +### Building blocks +############################################################################# + +
[docs]class NutHole: + """Draw a hex nut""" + sizes = { + "M1.6": (3.2, 1.3), + "M2": (4, 1.6), + "M2.5": (5, 2.0), + "M3": (5.5, 2.4), + "M4": (7, 3.2), + "M5": (8, 4.7), + "M6": (10, 5.2), + "M8": (13.7, 6.8), + "M10": (16, 8.4), + "M12": (18, 10.8), + "M14": (21, 12.8), + "M16": (24, 14.8), + "M20": (30, 18.0), + "M24": (36, 21.5), + "M30": (46, 25.6), + "M36": (55, 31), + "M42": (65, 34), + "M48": (75, 38), + "M56": (85, 45), + "M64": (95, 51), + } + + def __init__(self, boxes, settings): + self.boxes = boxes + self.ctx = boxes.ctx + self.settings = settings + + def __getattr__(self, name): + return getattr(self.boxes, name) + + @restore + @holeCol + def __call__(self, size, x=0, y=0, angle=0): + size = self.sizes.get(size, (size,))[0] + side = size / 3 ** 0.5 + self.boxes.moveTo(x, y, angle) + self.boxes.moveTo(-0.5 * side, 0.5 * size, angle) + for i in range(6): + self.boxes.edge(side) + self.boxes.corner(-60)
+ + +############################################################################## +### Argument types +############################################################################## + +
[docs]def argparseSections(s): + """ + Parse sections parameter + + :param s: string to parse + + """ + + result = [] + + s = re.split(r"\s|:", s) + + try: + for part in s: + m = re.match(r"^(\d+(\.\d+)?)/(\d+)$", part) + if m: + n = int(m.group(3)) + result.extend([float(m.group(1)) / n] * n) + continue + m = re.match(r"^(\d+(\.\d+)?)\*(\d+)$", part) + if m: + n = int(m.group(3)) + result.extend([float(m.group(1))] * n) + continue + result.append(float(part)) + except ValueError: + raise argparse.ArgumentTypeError("Don't understand sections string") + + if not result: + result.append(0.0) + + return result
+ +
[docs]class ArgparseEdgeType: + """argparse type to select from a set of edge types""" + + names = edges.getDescriptions() + edges = [] + + def __init__(self, edges=None): + if edges: + self.edges = list(edges) + + def __call__(self, pattern): + if len(pattern) != 1: + raise ValueError("Edge type can only have one letter.") + if pattern not in self.edges: + raise ValueError("Use one of the following values: " + + ", ".join(edges)) + return pattern + +
[docs] def html(self, name, default, translate): + options = "\n".join( + ("""<option value="%s"%s>%s</option>""" % + (e, ' selected="selected"' if e == default else "", + translate("%s %s" % (e, self.names.get(e, "")))) for e in self.edges)) + return """<select name="%s" id="%s" aria-labeledby="%s %s" size="1">\n%s</select>\n""" % (name, name, name+"_id", name+"_description", options)
+ +
[docs] def inx(self, name, viewname, arg): + return (' <param name="%s" type="optiongroup" appearance="combo" gui-text="%s" gui-description=%s>\n' % + (name, viewname, quoteattr(arg.help or "")) + + ''.join((' <option value="%s">%s %s</option>\n' % ( + e, e, self.names.get(e, "")) + for e in self.edges)) + + ' </param>\n')
+ +
[docs]class BoolArg: + def __call__(self, arg): + if not arg or arg.lower() in ("none", "0", "off", "false"): + return False + return True + +
[docs] def html(self, name, default, _): + if isinstance(default, (str)): + default = self(default) + return """<input name="%s" type="hidden" value="0"> +<input name="%s" id="%s" aria-labeledby="%s %s" type="checkbox" value="1"%s>""" % \ + (name, name, name, name+"_id", name+"_description",' checked="checked"' if default else "")
+ +boolarg = BoolArg() + + +
[docs]class HexHolesSettings(edges.Settings): + """Settings for hexagonal hole patterns + +Values: + +* absolute + * diameter : 5.0 : diameter of the holes + * distance : 3.0 : distance between the holes + * style : "circle" : currently only supported style + +""" + + absolute_params = { + 'diameter' : 10.0, + 'distance' : 3.0, + 'style' : ('circle', ), + } + + relative_params = {}
+ +
[docs]class fillHolesSettings(edges.Settings): + """Settings for Hole filling + +Values: + +* absolute + * fill_pattern : "no fill" : style of hole pattern + * hole_style : "round" : style of holes (does not apply to fill patterns 'vbar' and 'hbar') + * max_random : 1000 : maximum number of random holes + * bar_length : 50 : maximum length of bars + * hole_max_radius : 12.0 : maximum radius of generated holes (in mm) + * hole_min_radius : 4.0 : minimum radius of generated holes (in mm) + * space_between_holes : 4.0 : hole to hole spacing (in mm) + * space_to_border : 4.0 : hole to border spacing (in mm) + +""" + + absolute_params = { + "fill_pattern": ("no fill", "hex", "square", "random", "hbar", "vbar"), + "hole_style": ("round", "triangle", "square", "hexagon", "octagon"), + "max_random": 1000, + "bar_length": 50, + "hole_max_radius": 3.0, + "hole_min_radius": 0.5, + "space_between_holes": 4.0, + "space_to_border": 4.0, + }
+ +############################################################################## +### Main class +############################################################################## + +
[docs]class Boxes: + """Main class -- Generator should sub class this """ + + webinterface = True + ui_group = "Misc" + + description = "" # Markdown syntax is supported + +
[docs] def __init__(self): + self.formats = formats.Formats() + self.ctx = None + description = self.__doc__ + if self.description: + description += "\n\n" + self.description + self.argparser = ArgumentParser(description=description) + self.edgesettings = {} + self.inkscapefile = None + + self.metadata = { + "name" : self.__class__.__name__, + "short_description" : self.__doc__, + "description" : self.description, + "group" : self.ui_group, + "url" : "", + "command_line" : "" + } + + self.argparser._action_groups[1].title = self.__class__.__name__ + " Settings" + defaultgroup = self.argparser.add_argument_group( + "Default Settings") + defaultgroup.add_argument( + "--thickness", action="store", type=float, default=3.0, + help="thickness of the material (in mm) [\U0001F6C8](https://florianfesti.github.io/boxes/html/usermanual.html#thickness)") + defaultgroup.add_argument( + "--output", action="store", type=str, default="box.svg", + help="name of resulting file") + defaultgroup.add_argument( + "--format", action="store", type=str, default="svg", + choices=self.formats.getFormats(), + help="format of resulting file [\U0001F6C8](https://florianfesti.github.io/boxes/html/usermanual.html#format)") + defaultgroup.add_argument( + "--tabs", action="store", type=float, default=0.0, + help="width of tabs holding the parts in place (in mm)(not supported everywhere) [\U0001F6C8](https://florianfesti.github.io/boxes/html/usermanual.html#tabs)") + defaultgroup.add_argument( + "--debug", action="store", type=boolarg, default=False, + help="print surrounding boxes for some structures [\U0001F6C8](https://florianfesti.github.io/boxes/html/usermanual.html#debug)") + defaultgroup.add_argument( + "--labels", action="store", type=boolarg, default=True, + help="label the parts (where available)") + defaultgroup.add_argument( + "--reference", action="store", type=float, default=100, + help="print reference rectangle with given length (in mm)(zero to disable) [\U0001F6C8](https://florianfesti.github.io/boxes/html/usermanual.html#reference)") + defaultgroup.add_argument( + "--inner_corners", action="store", type=str, default="loop", + choices=["loop", "corner", "backarc"], + help="style for inner corners [\U0001F6C8](https://florianfesti.github.io/boxes/html/usermanual.html#inner-corners)") + defaultgroup.add_argument( + "--burn", action="store", type=float, default=0.1, + help='burn correction (in mm)(bigger values for tighter fit) [\U0001F6C8](https://florianfesti.github.io/boxes/html/usermanual.html#burn)')
+ +
[docs] @contextmanager + def saved_context(self): + """ + Generator: for saving and restoring contexts. + """ + cr = self.ctx + cr.save() + try: + yield cr + finally: + cr.restore()
+ +
[docs] def set_source_color(self, color): + """ + Sets the color of the pen. + """ + self.ctx.set_source_rgb(*color)
+ +
[docs] def set_font(self, style, bold=False, italic=False): + """ + Set font style used + :param style: "serif", "sans-serif" or "monospaced" + :param bold: Use bold font + :param italic: Use italic font + """ + self.ctx.set_font(style, bold, italic)
+ +
[docs] def open(self): + """ + Prepare for rendering + + Create canvas and edge and other objects + Call this before .render() + """ + if self.ctx is not None: + return + + self.bedBoltSettings = (3, 5.5, 2, 20, 15) # d, d_nut, h_nut, l, l1 + self.surface, self.ctx = self.formats.getSurface(self.format, self.output) + + if self.format == 'svg_Ponoko': + self.ctx.set_line_width(0.01) + self.set_source_color(Color.BLUE) + else: + self.ctx.set_line_width(max(2 * self.burn, 0.05)) + self.set_source_color(Color.BLACK) + + self.spacing = 2 * self.burn + 0.5 * self.thickness + self.set_font("sans-serif") + self._buildObjects() + if self.reference and self.format != 'svg_Ponoko': + self.move(self.reference, 10, "up", before=True) + self.ctx.rectangle(0, 0, self.reference, 10) + if self.reference < 80: + self.text("%.fmm, burn:%.2fmm" % (self.reference , self.burn), self.reference + 5, 5, + fontsize=8, align="middle left", color=Color.ANNOTATIONS) + else: + self.text("%.fmm, burn:%.2fmm" % (self.reference , self.burn), self.reference / 2.0, 5, + fontsize=8, align="middle center", color=Color.ANNOTATIONS) + self.move(self.reference, 10, "up") + self.ctx.stroke()
+ +
[docs] def buildArgParser(self, *l, **kw): + """ + Add commonly used arguments + + :param \*l: parameter names + :param \*\*kw: parameters with new default values + + Supported parameters are + + * floats: x, y, h, hi + * argparseSections: sx, sy, sh + * ArgparseEdgeType: bottom_edge, top_edge + * boolarg: outside + * str (selection): nema_mount + """ + for arg in l: + kw[arg] = None + for arg, default in kw.items(): + if arg == "x": + if default is None: default = 100.0 + help = "inner width in mm" + if "outside" in kw: + help += " (unless outside selected)" + self.argparser.add_argument( + "--x", action="store", type=float, default=default, + help=help) + elif arg == "y": + if default is None: default = 100.0 + help = "inner depth in mm" + if "outside" in kw: + help += " (unless outside selected)" + self.argparser.add_argument( + "--y", action="store", type=float, default=default, + help=help) + elif arg == "sx": + if default is None: default = "50*3" + self.argparser.add_argument( + "--sx", action="store", type=argparseSections, + default=default, + help="""sections left to right in mm [\U0001F6C8](https://florianfesti.github.io/boxes/html/usermanual.html#section-parameters)""") + elif arg == "sy": + if default is None: default = "50*3" + self.argparser.add_argument( + "--sy", action="store", type=argparseSections, + default=default, + help="""sections back to front in mm [\U0001F6C8](https://florianfesti.github.io/boxes/html/usermanual.html#section-parameters)""") + elif arg == "sh": + if default is None: default = "50*3" + self.argparser.add_argument( + "--sh", action="store", type=argparseSections, + default=default, + help="""sections bottom to top in mm [\U0001F6C8](https://florianfesti.github.io/boxes/html/usermanual.html#section-parameters)""") + elif arg == "h": + if default is None: default = 100.0 + help = "inner height in mm" + if "outside" in kw: + help += " (unless outside selected)" + self.argparser.add_argument( + "--h", action="store", type=float, default=default, + help=help) + elif arg == "hi": + if default is None: default = 0.0 + self.argparser.add_argument( + "--hi", action="store", type=float, default=default, + help="inner height of inner walls in mm (unless outside selected)(leave to zero for same as outer walls)") + elif arg == "hole_dD": + if default is None: default = "3.5:6.5" + self.argparser.add_argument( + "--hole_dD", action="store", type=argparseSections, default=default, + help="mounting hole diameter (shaft:head) in mm [\U0001F6C8](https://florianfesti.github.io/boxes/html/usermanual.html#mounting-holes)") + elif arg == "bottom_edge": + if default is None: default = "h" + self.argparser.add_argument( + "--bottom_edge", action="store", + type=ArgparseEdgeType("Fhse"), choices=list("Fhse"), + default=default, + help="edge type for bottom edge") + elif arg == "top_edge": + if default is None: default = "e" + self.argparser.add_argument( + "--top_edge", action="store", + type=ArgparseEdgeType("efFhcESŠikvLtGyY"), choices=list("efFhcESŠikvfLtGyY"), + default=default, help="edge type for top edge") + elif arg == "outside": + if default is None: default = True + self.argparser.add_argument( + "--outside", action="store", type=boolarg, default=default, + help="treat sizes as outside measurements [\U0001F6C8](https://florianfesti.github.io/boxes/html/usermanual.html#outside)") + elif arg == "nema_mount": + if default is None: default = 23 + self.argparser.add_argument( + "--nema_mount", action="store", + type=int, choices=list(sorted(self.nema_sizes.keys())), + default=default, help="NEMA size of motor") + else: + raise ValueError("No default for argument", arg)
+ +
[docs] def addSettingsArgs(self, settings, prefix=None, **defaults): + prefix = prefix or settings.__name__[:-len("Settings")] + settings.parserArguments(self.argparser, prefix, **defaults) + self.edgesettings[prefix] = {}
+ + +
[docs] def parseArgs(self, args=None): + """ + Parse command line parameters + + :param args: (Default value = None) parameters, None for using sys.argv + + """ + if args is None: + args = sys.argv[1:] + if len(args) > 1 and args[-1][0] != "-": + self.inkscapefile = args[-1] + del args[-1] + args = [a for a in args if not a.startswith('--tab=')] + + def cliquote(s): + s = s.replace('\r', '') + s = s.replace('\n', "\\n") + return quote(s) + + self.metadata["cli"] = "boxes " + self.__class__.__name__ + " " + " ".join((cliquote(arg) for arg in args)) + for key, value in vars(self.argparser.parse_args(args=args)).items(): + # treat edge settings separately + for setting in self.edgesettings: + if key.startswith(setting + '_'): + self.edgesettings[setting][key[len(setting)+1:]] = value + continue + setattr(self, key, value) + + # Change file ending to format if not given explicitly + format = getattr(self, "format", "svg") + if getattr(self, 'output', None) == 'box.svg': + self.output = 'box.' + format.split("_")[0]
+ +
[docs] def addPart(self, part, name=None): + """ + Add Edge or other part instance to this one and add it as attribute + + :param part: Callable + :param name: (Default value = None) attribute name (__name__ as default) + + """ + if name is None: + name = part.__class__.__name__ + name = name[0].lower() + name[1:] + # if not hasattr(self, name): + if isinstance(part, edges.BaseEdge): + self.edges[part.char] = part + else: + setattr(self, name, part)
+ +
[docs] def addParts(self, parts): + for part in parts: + self.addPart(part)
+ + def _buildObjects(self): + """Add default edges and parts """ + self.edges = {} + self.addPart(edges.Edge(self, None)) + self.addPart(edges.OutSetEdge(self, None)) + edges.GripSettings(self.thickness).edgeObjects(self) + + # Finger joints + # Share settings object + s = edges.FingerJointSettings(self.thickness, True, + **self.edgesettings.get("FingerJoint", {})) + s.edgeObjects(self) + self.addPart(edges.FingerHoles(self, s), name="fingerHolesAt") + # Stackable + edges.StackableSettings(self.thickness, True, + **self.edgesettings.get("Stackable", {})).edgeObjects(self) + # Dove tail joints + edges.DoveTailSettings(self.thickness, True, + **self.edgesettings.get("DoveTail", {})).edgeObjects(self) + # Flex + s = edges.FlexSettings(self.thickness, True, + **self.edgesettings.get("Flex", {})) + self.addPart(edges.FlexEdge(self, s)) + # Clickable + edges.ClickSettings(self.thickness, True, + **self.edgesettings.get("Click", {})).edgeObjects(self) + # Hinges + edges.HingeSettings(self.thickness, True, + **self.edgesettings.get("Hinge", {})).edgeObjects(self) + edges.ChestHingeSettings(self.thickness, True, + **self.edgesettings.get("ChestHinge", {})).edgeObjects(self) + edges.CabinetHingeSettings(self.thickness, True, + **self.edgesettings.get("CabinetHinge", {})).edgeObjects(self) + # Sliding Lid + edges.LidSettings(self.thickness, True, + **self.edgesettings.get("Lid", {})).edgeObjects(self) + # Rounded Triangle Edge + edges.RoundedTriangleEdgeSettings(self.thickness, True, + **self.edgesettings.get("RoundedTriangleEdge", {})).edgeObjects(self) + # Grooved Edge + edges.GroovedSettings(self.thickness, True, + **self.edgesettings.get("Grooved", {})).edgeObjects(self) + # Mounting Edge + edges.MountingSettings(self.thickness, True, + **self.edgesettings.get("Mounting", {})).edgeObjects(self) + # Handle Edge + edges.HandleEdgeSettings(self.thickness, True, + **self.edgesettings.get("HandleEdge", {})).edgeObjects(self) + # HexHoles + self.hexHolesSettings = HexHolesSettings(self.thickness, True, + **self.edgesettings.get("HexHoles", {})) + + # Nuts + self.addPart(NutHole(self, None)) + # Gears + self.addPart(gears.Gears(self)) + s = edges.GearSettings(self.thickness, True, + **self.edgesettings.get("Gear", {})) + self.addPart(edges.RackEdge(self, s)) + self.addPart(pulley.Pulley(self)) + self.addPart(parts.Parts(self)) + +
[docs] def adjustSize(self, l, e1=True, e2=True): + # Char to edge object + e1 = self.edges.get(e1, e1) + e2 = self.edges.get(e2, e2) + + try: + total = sum(l) + walls = (len(l) - 1) * self.thickness + except TypeError: + total = l + walls = 0 + + if isinstance(e1, edges.BaseEdge): + walls += e1.startwidth() + e1.margin() + elif e1: + walls += self.thickness + + if isinstance(e2, edges.BaseEdge): + walls += e2.startwidth() + e2.margin() + elif e2: + walls += self.thickness + + try: + if total > 0.0: + factor = (total - walls) / total + else: + factor = 1.0 + return [s * factor for s in l] + except TypeError: + return l - walls
+ +
[docs] def render(self): + """Implement this method in your sub class. + + You will typically need to call .parseArgs() before calling this one""" + self.open() + # Change settings and create new Edges and part classes here + raise NotImplementedError + self.close()
+ +
[docs] def cc(self, callback, number, x=0.0, y=None): + """Call callback from edge of a part + + :param callback: callback (callable or list of callables) + :param number: number of the callback + :param x: (Default value = 0.0) x position to be call on + :param y: (Default value = None) y position to be called on (default does burn correction) + + """ + if y is None: + y = self.burn + + if hasattr(callback, '__getitem__'): + try: + callback = callback[number] + number = None + except (KeyError, IndexError): + pass + + if callback and callable(callback): + with self.saved_context(): + self.moveTo(x, y) + if number is None: + callback() + else: + callback(number) + self.ctx.move_to(0, 0)
+ +
[docs] def getEntry(self, param, idx): + """ + Get entry from list or items itself + + :param param: list or item + :param idx: index in list + + """ + if isinstance(param, list): + if len(param) > idx: + return param[idx] + else: + return None + else: + return param
+ +
[docs] def close(self): + """Finish rendering + + Flush canvas to disk and convert output to requested format if needed. + Call after .render()""" + if self.ctx == None: + return + + self.ctx.stroke() + self.ctx = None + + self.surface.set_metadata(self.metadata) + + self.surface.flush() + self.surface.finish(self.inner_corners) + + self.formats.convert(self.output, self.format, self.metadata) + if self.inkscapefile: + try: + out = sys.stdout.buffer + except AttributeError: + out= sys.stdout + svgutil.svgMerge(self.output, self.inkscapefile, out)
+ + ############################################################ + ### Turtle graphics commands + ############################################################ + +
[docs] def corner(self, degrees, radius=0, tabs=0): + """ + Draw a corner + + This is what does the burn corrections + + :param degrees: angle + :param radius: (Default value = 0) + + """ + + try: + degrees, radius = degrees + except: + pass + + rad = degrees * math.pi / 180 + + if tabs and self.tabs: + if degrees > 0: + r_ = radius + self.burn + tabrad = self.tabs / max(r_, 0.01) + else: + r_ = radius - self.burn + tabrad = -self.tabs / max(r_, 0.01) + + length = abs(r_ * rad) + tabs = min(tabs, int(length // (tabs*3*self.tabs))) + if tabs and self.tabs: + l = (length - tabs * self.tabs) / tabs + lang = math.degrees(l / r_) + if degrees < 0: + lang = -lang + #print(degrees, radius, l, lang, tabs, math.degrees(tabrad)) + self.corner(lang/2., radius) + for i in range(tabs-1): + self.moveArc(math.degrees(tabrad), r_) + self.corner(lang, radius) + if tabs: + self.moveArc(math.degrees(tabrad), r_) + self.corner(lang/2., radius) + return + + if ((radius > 0.5* self.burn and abs(degrees) > 36) or + (abs(degrees) > 100)): + steps = int(abs(degrees)/ 36.) + 1 + for i in range(steps): + self.corner(float(degrees)/steps, radius) + return + + if degrees > 0: + self.ctx.arc(0, radius + self.burn, radius + self.burn, + -0.5 * math.pi, rad - 0.5 * math.pi) + elif radius > self.burn: + self.ctx.arc_negative(0, -(radius - self.burn), radius - self.burn, + 0.5 * math.pi, rad + 0.5 * math.pi) + else: # not rounded inner corner + self.ctx.arc_negative(0, self.burn - radius, self.burn - radius, + -0.5 * math.pi, -0.5 * math.pi + rad) + + self._continueDirection(rad)
+ +
[docs] def edge(self, length, tabs=0): + """ + Simple line + :param length: length in mm + + """ + self.ctx.move_to(0, 0) + if tabs and self.tabs: + if self.tabs > length: + self.ctx.move_to(length, 0) + else: + tabs = min(tabs, max(1, int(length // (tabs*3*self.tabs)))) + l = (length - tabs * self.tabs) / tabs + self.ctx.line_to(0.5*l, 0) + for i in range(tabs-1): + self.ctx.move_to((i+0.5)*l+self.tabs, 0) + self.ctx.line_to((i+0.5)*l+self.tabs+l, 0) + if tabs == 1: + self.ctx.move_to((tabs-0.5)*l+self.tabs, 0) + else: + self.ctx.move_to((tabs-0.5)*l+2*self.tabs, 0) + + self.ctx.line_to(length, 0) + else: + self.ctx.line_to(length, 0) + self.ctx.translate(*self.ctx.get_current_point())
+ +
[docs] def step(self, out): + """ + Create a parallel step prependicular to the current direction + Positive values move to the outside of the part + """ + if out > 1E-5: + self.corner(-90) + self.edge(out) + self.corner(90) + elif out < -1E-5: + self.corner(90) + self.edge(-out) + self.corner(-90)
+ +
[docs] def curveTo(self, x1, y1, x2, y2, x3, y3): + """control point 1, control point 2, end point + + :param x1: + :param y1: + :param x2: + :param y2: + :param x3: + :param y3: + + """ + self.ctx.curve_to(x1, y1, x2, y2, x3, y3) + dx = x3 - x2 + dy = y3 - y2 + rad = math.atan2(dy, dx) + self._continueDirection(rad)
+ +
[docs] def polyline(self, *args): + """ + Draw multiple connected lines + + :param \*args: Alternating length in mm and angle in degrees. + + lengths may be a tuple (length, #tabs) + angles may be tuple (angle, radius) + """ + for i, arg in enumerate(args): + if i % 2: + if isinstance(arg, tuple): + self.corner(*arg) + else: + self.corner(arg) + else: + if isinstance(arg, tuple): + self.edge(*arg) + else: + self.edge(arg)
+ +
[docs] def bedBoltHole(self, length, bedBoltSettings=None, tabs=0): + """ + Draw an edge with slot for a bed bolt + + :param length: length of the edge in mm + :param bedBoltSettings: (Default value = None) Dimmensions of the slot + + """ + d, d_nut, h_nut, l, l1 = bedBoltSettings or self.bedBoltSettings + self.edge((length - d) / 2.0, tabs=tabs//2) + self.corner(90) + self.edge(l1) + self.corner(90) + self.edge((d_nut - d) / 2.0) + self.corner(-90) + self.edge(h_nut) + self.corner(-90) + self.edge((d_nut - d) / 2.0) + self.corner(90) + self.edge(l - l1 - h_nut) + self.corner(-90) + self.edge(d) + self.corner(-90) + self.edge(l - l1 - h_nut) + self.corner(90) + self.edge((d_nut - d) / 2.0) + self.corner(-90) + self.edge(h_nut) + self.corner(-90) + self.edge((d_nut - d) / 2.0) + self.corner(90) + self.edge(l1) + self.corner(90) + self.edge((length - d) / 2.0, tabs=tabs-(tabs//2))
+ +
[docs] def edgeCorner(self, edge1, edge2, angle=90): + """Make a corner between two Edges. Take width of edges into account""" + edge1 = self.edges.get(edge1, edge1) + edge2 = self.edges.get(edge2, edge2) + + self.edge(edge2.startwidth() * math.tan(math.radians(angle/2.))) + self.corner(angle) + self.edge(edge1.endwidth() * math.tan(math.radians(angle/2.)))
+ +
[docs] def regularPolygon(self, corners=3, radius=None, h=None, side=None): + """Give measures of a regular polygon + + :param corners: number of corners of the polygon + :param radius: distance center to one of the corners + :param h: distance center to one of the sides (height of sector) + :param side: length of one side + :return: (radius, h, side) + """ + if radius: + side = 2 * math.sin(math.radians(180.0/corners)) * radius + h = radius * math.cos(math.radians(180.0/corners)) + elif h: + side = 2 * math.tan(math.radians(180.0/corners)) * h + radius = ((side/2.)**2+h**2)**0.5 + elif side: + h = 0.5 * side * math.tan(math.radians(90-180./corners)) + radius = ((side/2.)**2+h**2)**0.5 + + return radius, h, side
+ +
[docs] @restore + def regularPolygonAt(self, x, y, corners, angle=0, r=None, h=None, side=None): + """Draw regular polygon""" + self.moveTo(x, y, angle) + r, h, side = self.regularPolygon(corners, r, h, side) + self.moveTo(-side/2.0, -h-self.burn) + for i in range(corners): + self.edge(side) + self.corner(360./corners)
+ +
[docs] def regularPolygonWall(self, corners=3, r=None, h=None, side=None, + edges='e', hole=None, callback=None, move=None): + """Create regular polygon as a wall + + :param corners: number of corners of the polygon + :param radius: distance center to one of the corners + :param h: distance center to one of the sides (height of sector) + :param side: length of one side + :param edges: (Default value = "e", may be string/list of length corners) + :param hole: diameter of central hole (Default value = 0) + :param callback: (Default value = None, middle=0, then sides=1..) + :param move: (Default value = None) + """ + r, h, side = self.regularPolygon(corners, r, h, side) + + t = self.thickness + + if not hasattr(edges, "__getitem__") or len(edges) == 1: + edges = [edges] * corners + edges = [self.edges.get(e, e) for e in edges] + edges += edges # append for wrapping around + + if corners % 2: + th = r + h + edges[0].spacing() + ( + max(edges[corners//2].spacing(), + edges[corners//2+1].spacing()) / + math.sin(math.radians(90-180/corners))) + else: + th = 2*h + edges[0].spacing() + edges[corners//2].spacing() + + tw = 0 + for i in range(corners): + ang = (180+360*i)/corners + tw = max(tw, 2*abs(math.sin(math.radians(ang))* + (r + max(edges[i].spacing(), edges[i+1].spacing())/ + math.sin(math.radians(90-180/corners))))) + + if self.move(tw, th, move, before=True): + return + + self.moveTo(0.5*tw-0.5*side, edges[0].margin()) + + + if hole: + self.hole(side/2., h+edges[0].startwidth() + self.burn, hole/2.) + self.cc(callback, 0, side/2., h+edges[0].startwidth() + self.burn) + for i in range(corners): + self.cc(callback, i+1, 0, edges[i].startwidth() + self.burn) + edges[i](side) + self.edgeCorner(edges[i], edges[i+1], 360.0/corners) + + self.move(tw, th, move)
+ +
[docs] def grip(self, length, depth): + """Corrugated edge useful as an gipping area + + :param length: length + :param depth: depth of the grooves + + """ + grooves = max(int(length // (depth * 2.0)) + 1, 1) + depth = length / grooves / 4.0 + for groove in range(grooves): + self.corner(90, depth) + self.corner(-180, depth) + self.corner(90, depth)
+ + def _latchHole(self, length): + """ + + :param length: + + """ + self.edge(1.1 * self.thickness) + self.corner(-90) + self.edge(length / 2.0 + 0.2 * self.thickness) + self.corner(-90) + self.edge(1.1 * self.thickness) + + def _latchGrip(self, length): + """ + + :param length: + + """ + self.corner(90, self.thickness / 4.0) + self.grip(length / 2.0 - self.thickness / 2.0 - 0.2 * self.thickness, self.thickness / 2.0) + self.corner(90, self.thickness / 4.0) + +
[docs] def latch(self, length, positive=True, reverse=False): + """Latch to fix a flex box door to the box + + :param length: length in mm + :param positive: (Default value = True) False: Door side; True: Box side + :param reverse: (Default value = False) True when running away from the latch + + """ + if positive: + if reverse: + self.edge(length / 2.0) + self.corner(-90) + self.edge(self.thickness) + self.corner(90) + self.edge(length / 2.0) + self.corner(90) + self.edge(self.thickness) + self.corner(-90) + if not reverse: + self.edge(length / 2.0) + else: + if reverse: + self._latchGrip(length) + else: + self.corner(90) + self._latchHole(length) + if not reverse: + self._latchGrip(length) + else: + self.corner(90)
+ +
[docs] def handle(self, x, h, hl, r=30): + """Creates an Edge with a handle + + :param x: width in mm + :param h: height in mm + :param hl: height if th grip hole + :param r: (Default value = 30) radius of the corners + + """ + d = (x - hl - 2 * r) / 2.0 + + # Hole + with self.saved_context(): + self.moveTo(d + 2 * r, 0) + self.edge(hl - 2 * r) + self.corner(-90, r) + self.edge(h - 3 * r) + self.corner(-90, r) + self.edge(hl - 2 * r) + self.corner(-90, r) + self.edge(h - 3 * r) + self.corner(-90, r) + + self.moveTo(0, 0) + + self.curveTo(d, 0, d, 0, d, -h + r) + self.curveTo(r, 0, r, 0, r, r) + self.edge(hl) + self.curveTo(r, 0, r, 0, r, r) + self.curveTo(h - r, 0, h - r, 0, h - r, -d)
+ + ### Navigation + +
[docs] def moveTo(self, x, y=0.0, degrees=0): + """ + Move coordinate system to given point + + :param x: + :param y: (Default value = 0.0) + :param degrees: (Default value = 0) + + """ + self.ctx.move_to(0, 0) + self.ctx.translate(x, y) + self.ctx.rotate(degrees * math.pi / 180.0) + self.ctx.move_to(0, 0)
+ +
[docs] def moveArc(self, angle, r=0.0): + """ + :param angle: + :param r: (Default value = 0.0) + """ + if r < 0: + r = -r + angle = -angle + + rad = math.radians(angle) + if angle > 0: + self.moveTo(r*math.sin(rad), + r*(1-math.cos(rad)), angle) + else: + self.moveTo(r*math.sin(-rad), + -r*(1-math.cos(rad)), angle)
+ + def _continueDirection(self, angle=0): + """ + Set coordinate system to current position (end point) + + :param angle: (Default value = 0) heading + + """ + self.ctx.translate(*self.ctx.get_current_point()) + self.ctx.rotate(angle) + +
[docs] def move(self, x, y, where, before=False, label=""): + """Intended to be used by parts + where can be combinations of "up" or "down", "left" or "right", "only", + "mirror" and "rotated" + when "only" is included the move is only done when before is True + "mirror" will flip the part along the y axis + "rotated" draws the parts rotated 90 counter clockwise + The function returns whether actual drawing of the part + should be ommited. + + :param x: width of part + :param y: height of part + :param where: which direction to move + :param before: (Default value = False) called before or after part being drawn + + """ + if not where: + where = "" + + terms = where.split() + dontdraw = before and "only" in terms + + x += self.spacing + y += self.spacing + + if "rotated" in terms: + x, y = y, x + + moves = { + "up": (0, y, False), + "down": (0, -y, True), + "left": (-x, 0, True), + "right": (x, 0, False), + "only": (0, 0, None), + "mirror": (0, 0, None), + "rotated": (0, 0, None), + } + + if not before: + # restore position + self.ctx.restore() + if self.labels and label: + self.text(label, x/2, y/2, align="middle center", color=Color.ANNOTATIONS, fontsize=4) + self.ctx.stroke() + + for term in terms: + if not term in moves: + raise ValueError("Unknown direction: '%s'" % term) + mx, my, movebeforeprint = moves[term] + if movebeforeprint and before: + self.moveTo(mx, my) + elif (not movebeforeprint and not before) or dontdraw: + self.moveTo(mx, my) + if not dontdraw: + if before: + # paint debug rectangle + if self.debug: + with self.saved_context(): + self.set_source_color(Color.ANNOTATIONS) + self.ctx.rectangle(0, 0, x, y) + # save position + self.ctx.save() + if "rotated" in terms: + self.moveTo(x, 0, 90) + x, y = y, x # change back for "mirror" + if "mirror" in terms: + self.moveTo(x, 0) + self.ctx.scale(-1, 1) + self.moveTo(self.spacing / 2.0, self.spacing / 2.0) + self.ctx.new_part() + + return dontdraw
+ +
[docs] @restore + def circle(self, x, y, r): + """ + Draw a round disc + + :param x: position + :param y: postion + :param r: radius + + """ + r += self.burn + self.moveTo(x + r, y) + a = 0 + n = 10 + da = 2 * math.pi / n + for i in range(n): + self.ctx.arc(-r, 0, r, a, a+da) + a += da + self.ctx.stroke()
+ +
[docs] @restore + @holeCol + def regularPolygonHole(self, x, y, r=0.0, d=0.0, n=6, a=0.0, tabs=0, corner_radius=0.0): + """ + Draw a hole in shape of an n-edged regular polygon + + :param x: position + :param y: postion + :param r: radius + :param n: number of edges + :param a: rotation angle + + """ + + if not r: + r = d / 2.0 + + if n == 0: + self.hole(x, y, r=r, tabs=tabs) + return + + if r < self.burn: + r = self.burn + 1E-9 + r_ = r - self.burn + + if corner_radius < self.burn: + corner_radius = self.burn + cr_ = corner_radius - self.burn + + side_length = 2 * r_ * math.sin(math.pi / n) + apothem = r_ * math.cos(math.pi / n) + # the corner chord: + s = math.sqrt(2 * math.pow(cr_, 2) * (1 - math.cos(2 * math.pi / n))) + # the missing portion of the rounded corner: + b = math.sin(math.pi / n) / math.sin(2 * math.pi / n) * s + # the flat portion of the side: + flat_side_length = side_length - 2 * b + + self.moveTo(x, y, a) + self.moveTo(r_, 0, 90+180/n) + self.moveTo(b, 0, 0) + for _ in range(n): + self.edge(flat_side_length) + self.corner(360/n, cr_)
+ +
[docs] @restore + @holeCol + def hole(self, x, y, r=0.0, d=0.0, tabs=0): + """ + Draw a round hole + + :param x: position + :param y: postion + :param r: radius + + """ + + if not r: + r = d / 2.0 + if r < self.burn: + r = self.burn + 1E-9 + r_ = r - self.burn + self.moveTo(x + r_, y, -90) + self.corner(-360, r, tabs)
+ +
[docs] @restore + @holeCol + def rectangularHole(self, x, y, dx, dy, r=0, center_x=True, center_y=True): + """ + Draw a rectangular hole + + :param x: position + :param y: position + :param dx: width + :param dy: height + :param r: (Default value = 0) radius of the corners + :param center_x: (Default value = True) if True, x position is the center, else the start + :param center_y: (Default value = True) if True, y position is the center, else the start + + """ + r = min(r, dx/2., dy/2.) + x_start = x if center_x else x + dx / 2.0 + y_start = y - dy / 2.0 if center_y else y + self.moveTo(x_start, y_start + self.burn, 180) + self.edge(dx / 2.0 - r) # start with an edge to allow easier change of inner corners + for d in (dy, dx, dy, dx / 2.0 + r): + self.corner(-90, r) + self.edge(d - 2 * r)
+ +
[docs] @restore + @holeCol + def dHole(self, x, y, r=None, d=None, w=None, rel_w=0.75, angle=0): + """ + Draw a hole for a shaft with flat edge - D shaped hole + + :param x: center position + :param y: center position + :param r: radius (overrides d) + :param d: diameter + :param w: width measured against flat side in mm + :param rel_w: width in percent + :param angle: orentation (rotation) of the flat side + + """ + + if r is None: + r = d / 2.0 + if w is None: + w = 2.0 * r * rel_w + w -= r + if r <= 0.0: + return + if abs(w) > r: + return self.hole(x, y, r) + + a = math.degrees(math.acos(w / r)) + self.moveTo(x, y, angle-a) + self.moveTo(r-self.burn, 0, -90) + self.corner(-360+2*a, r) + self.corner(-a) + self.edge(2*r*math.sin(math.radians(a)))
+ +
[docs] @restore + @holeCol + def flatHole(self, x, y, r=None, d=None, w=None, rel_w=0.75, angle=0): + """ + Draw a hole for a shaft with two opposed flat edges - ( ) shaped hole + + :param x: center position + :param y: center position + :param r: radius (overrides d) + :param d: diameter + :param w: width measured against flat side in mm + :param rel_w: width in percent + :param angle: orientation (rotation) of the flat sides + + """ + + if r is None: + r = d / 2.0 + if w is None: + w = r * rel_w + else: + w = w / 2.0 + + if r < 0.0: + return + if abs(w) > r: + return self.hole(x, y, r) + + a = math.degrees(math.acos(w / r)) + self.moveTo(x, y, angle-a) + self.moveTo(r-self.burn, 0, -90) + for i in range(2): + self.corner(-180+2*a, r) + self.corner(-a) + self.edge(2*r*math.sin(math.radians(a))) + self.corner(-a)
+ +
[docs] @restore + @holeCol + def mountingHole(self, x, y, d_shaft, d_head=0.0, angle=0, tabs=0): + """ + Draw a pear shaped mounting hole for sliding over a screw head. Total height = 1.5* d_shaft + d_head + + :param x: position + :param y: postion + :param d_shaft: diameter of the screw shaft + :param d_head: diameter of the screw head + :param angle: rotation angle of the hole + + """ + + if d_shaft < (2 * self.burn): + return # no hole if diameter is smaller then the capabilities of the machine + + if not d_head or d_head < (2 * self.burn): # if no head diameter is given + self.hole(x, y ,d=d_shaft, tabs=tabs) # only a round hole is generated + return + + rs = d_shaft / 2 + rh = d_head / 2 + + self.moveTo(x, y, angle) + self.moveTo(0, rs - self.burn, 0) + self.corner(-180, rs, tabs) + self.edge(2 * rs,tabs) + a = math.degrees(math.asin(rs / rh)) + self.corner(90 - a, 0, tabs) + self.corner(-360 + 2 * a, rh, tabs) + self.corner(90 - a, 0, tabs) + self.edge(2 * rs, tabs)
+ +
[docs] @restore + def text(self, text, x=0, y=0, angle=0, align="", fontsize=10, color=[0.0, 0.0, 0.0], font="Arial"): + """ + Draw text + + :param text: text to render + :param x: (Default value = 0) + :param y: (Default value = 0) + :param angle: (Default value = 0) + :param align: (Default value = "") string with combinations of (top|middle|bottom) and (left|center|right) separated by a space + + """ + self.moveTo(x, y, angle) + text = text.split("\n") + + lines = len(text) + height = lines * fontsize + (lines - 1) * 0.4 * fontsize + align = align.split() + halign = "left" + moves = { + "top": -height, + "middle": -0.5 * height, + "bottom": 0, + "left": "left", + "center": "middle", + "right": "end", + } + for a in align: + if a in moves: + if isinstance(moves[a], str): + halign = moves[a] + else: + self.moveTo(0, moves[a]) + else: + raise ValueError("Unknown alignment: %s" % align) + + for line in reversed(text): + self.ctx.show_text(line, fs=fontsize, align=halign, rgb=color, font=font) + self.moveTo(0, 1.4 * fontsize)
+ + tx_sizes = { + 1 : 0.61, + 2 : 0.70, + 3 : 0.82, + 4 : 0.96, + 5 : 1.06, + 6 : 1.27, + 7 : 1.49, + 8 : 1.75, + 9 : 1.87, + 10 : 2.05, + 15 : 2.40, + 20 : 2.85, + 25 : 3.25, + 30 : 4.05, + 40 : 4.85, + 45 : 5.64, + 50 : 6.45, + 55 : 8.05, + 60 : 9.60, + 70 : 11.20, + 80 : 12.80, + 90 : 14.40, + 100 : 16.00, + } + +
[docs] @restore + @holeCol + def TX(self, size, x=0, y=0, angle=0): + """Draw a star pattern + + :param size: 1 to 100 + :param x: (Default value = 0) + :param y: (Default value = 0) + :param angle: (Default value = 0) + """ + self.moveTo(x, y, angle) + + size = self.tx_sizes.get(size, 0) + ri = 0.5 * size * math.tan(math.radians(30)) + ro = ri * (2**0.5-1) + + self.moveTo(size * 0.5 - self.burn, 0, -90) + for i in range(6): + self.corner(45, ri) + self.corner(-150, ro) + self.corner(45, ri)
+ + nema_sizes = { + # motor,flange, holes, screws + 8: (20.3, 16, 15.4, 3), + 11: (28.2, 22, 23, 4), + 14: (35.2, 22, 26, 4), + 16: (39.2, 22, 31, 4), + 17: (42.2, 22, 31, 4), + 23: (56.4, 38.1, 47.1, 5.2), + 24: (60, 36, 49.8, 5.1), + 34: (86.3, 73, 69.8, 6.6), + 42: (110, 55.5, 89, 8.5), + } + +
[docs] @restore + def NEMA(self, size, x=0, y=0, angle=0, screwholes=None): + """Draw holes for mounting a NEMA stepper motor + + :param size: Nominal size in tenths of inches + :param x: (Default value = 0) + :param y: (Default value = 0) + :param angle: (Default value = 0) + + """ + width, flange, holedistance, diameter = self.nema_sizes[size] + if screwholes: + diameter = screwholes + self.moveTo(x, y, angle) + if self.debug: + self.rectangularHole(0, 0, width, width) + self.hole(0, 0, 0.5 * flange) + for x in (-1, 1): + for y in (-1, 1): + self.hole(x * 0.5 * holedistance, + y * 0.5 * holedistance, + 0.5 * diameter)
+ +
[docs] @restore + def showBorderPoly(self,border,color=Color.ANNOTATIONS): + """ + draw border polygon (for debugging only) + + :param border: array with coordinate [(x0,y0), (x1,y1),...] of the border polygon + + """ + self.set_source_color(color) + self.ctx.save() + self.ctx.move_to(*border[0]) + + for x, y in border[1:]: + self.ctx.line_to(x, y) + + self.ctx.line_to(*border[0]) + self.ctx.restore() + + i = 0 + for x, y in border: + i += 1 + self.hole(x, y, 0.5, color=color) + self.text(str(i), x, y, fontsize=2, color=color)
+ +
[docs] @restore + @holeCol + def fillHoles(self, pattern, border, max_radius, hspace=3, bspace=0, min_radius=0.5, style="round", bar_length=50, max_random=1000): + """ + fill a polygon defined by its outline with holes + + :param pattern: defines the hole pattern - currently "random", "hex", "square" "hbar" or "vbar" are supported + :param border: array with coordinate [(x0,y0), (x1,y1),...] of the border polygon + :param max_radius: maximum hole radius + :param hspace: space between holes + :param bspace: space to border + :param min_radius: minimum hole radius + :param style: defines hole style - currently one of "round", "triangle", "square", "hexagon" or "octagon" + :param bar_length: maximum bar length + :param max_random: maximum number of random holes + + """ + if pattern not in ["random", "hex", "square", "hbar", "vbar"]: + return + + a = 0 + if style == "round": + n = 0 + elif style == "triangle": + n = 3 + a = 60 + elif style == "square": + n = 4 + elif style == "hexagon": + n = 6 + a = 30 + elif style == "octagon": + n = 8 + a = 22.5 + else: + raise ValueError("fillHoles - unknown hole style: %s)" % style) + +# note to myself: ^y x> + + if self.debug: + self.showBorderPoly(border) + + borderPoly = Polygon(border) + min_x, min_y, max_x, max_y = borderPoly.bounds + + if pattern == "vbar": + border = [(max_y - y + min_y, x) for x, y in border] + borderPoly = Polygon(border) + min_x, min_y, max_x, max_y = borderPoly.bounds + self.moveTo(0, max_x + min_x, -90) + pattern = "hbar" + if self.debug: + self.showBorderPoly(border, color=Color.MAGENTA) + + row = 0 + i = 0 + + # calc the next smaller radius to fit an 'optimum' number of circles + # for x direction + nx = math.ceil((max_x - min_x - 2 * bspace + hspace) / (2 * max_radius + hspace)) + max_radius_x = (max_x - min_x - 2 * bspace - (nx - 1) * hspace) / nx / 2 + + # for y direction + if pattern == "hex": + ny = math.ceil((max_y - min_y - 2 * bspace - 2 * max_radius) / (math.sqrt(3) / 2 * (2 * max_radius + hspace))) + max_radius_y = (max_y - min_y - 2 * bspace - math.sqrt(3) / 2 * ny * hspace) / (math.sqrt(3) * ny + 2 ) + else: + ny = math.ceil((max_y - min_y - 2 * bspace + hspace) / (2 * max_radius + hspace)) + max_radius_y = (max_y - min_y - 2 * bspace - (ny - 1) * hspace) / ny / 2 + + if pattern == "random": + grid = {} + misses = 0 # in a row + while i < max_random and misses < 20: + i += 1 + misses += 1 + # random new point + x = random.randrange(math.floor(min_x + bspace), math.ceil(max_x - bspace)) # randomness takes longer to compute + y = random.randrange(math.floor(min_y + bspace), math.ceil(max_y - bspace)) # but generates a new pattern for each run + pt = Point(x, y).buffer(min_radius + bspace) + # check if point is within border + if borderPoly.contains(pt): + pt1 = Point(x, y) + grid_x = int(x//(2*max_radius+hspace)) + grid_y = int(y//(2*max_radius+hspace)) + # compute distance between hole and border + bdist = borderPoly.exterior.distance(pt1) - bspace + # compute minimum distance to all other holes + hdist = max_radius + try: # learned from https://medium.com/techtofreedom/5-ways-to-break-out-of-nested-loops-in-python-4c505d34ace7 + for gx in (-1, 0, 1): + for gy in (-1, 0, 1): + for pt2 in grid.get((grid_x+gx, grid_y+gy), []): + pt3 = Point(pt2.x, pt2.y) + hdist = min(hdist, pt1.distance(pt3) - pt2.z - hspace) + if hdist < min_radius: + hdist = 0 + raise StopIteration + except StopIteration: + pass + # find maximum radius depending on distances + r = min(bdist, hdist) + # if too small, dismiss cycle + if r < min_radius: + continue + # if too large, limit to max size + if r > max_radius: + r = max_radius + # store in grid with radius as z value + grid.setdefault((grid_x, grid_y), []).append( + Point(x, y, r)) + misses = 0 + # and finally paint the hole + self.regularPolygonHole(x, y, r=r, n=n, a=a) + # rinse and repeat + + elif pattern in ("square", "hex"): + # use 'optimum' hole size + max_radius = min(max_radius_x, max_radius_y) + + # check if at least one line fits (we do horizontal filling) + if (max_y - min_y) < (2 * max_radius + 2 * bspace): + return + + # make cutPolys a little wider to avoid + # overlapping with lines to be cut + outerCutPoly = borderPoly.buffer(-1 * (bspace - 0.000001), + join_style=2) + outerTestPoly = borderPoly.buffer(-1 * (bspace - 0.01), + join_style=2) + # shrink original polygon to get place for full size polygons + innerCutPoly = borderPoly.buffer(-1 * (bspace + max_radius - 0.0001), join_style=2) + innerTestPoly = borderPoly.buffer(-1 * (bspace + max_radius - 0.001), join_style=2) + + # get left and right boundaries of cut polygon + x_cpl, y_cpl, x_cpr, y_cpr = outerCutPoly.bounds + + if self.debug: + self.showBorderPoly(list(outerCutPoly.exterior.coords)) + self.showBorderPoly(list(innerCutPoly.exterior.coords)) + + # set startpoint + y = min_y + bspace + max_radius_y + + while y < (max_y - bspace - max_radius_y): + if pattern == "square" or row % 2 == 0: + xs = min_x + bspace + max_radius_x + else: + xs = min_x + max_radius_x * 2 + hspace / 2 + bspace + + # create line segments cut by the polygons + line_complete = LineString([(x_cpl, y), (max_x + 1, y)]) + # cut accurate + outer_line_split = split(line_complete, outerCutPoly) + line_complete = LineString([(x_cpl, y), (max_x + 1, y)]) + inner_line_split = split(line_complete, innerCutPoly) + inner_line_index = 0 + + if self.debug and False: + for line in inner_line_split.geoms: + self.hole(line.bounds[0], line.bounds[1], 1.1) + self.hole(line.bounds[2], line.bounds[3], .9) + + # process each line + for line_this in outer_line_split.geoms: + + if self.debug and False: # enable to debug missing lines + x_start, y_start, x_end, y_end = line_this.bounds + with self.saved_context(): + self.moveTo(x_start, y_start ,0) + self.hole(0, 0, 0.5) + self.edge(x_end - x_start) + with self.saved_context(): + self.moveTo(x_start, y_start ,0) + self.text(str(outerTestPoly.contains(line_this)), 0, 0, fontsize=2, color=Color.ANNOTATIONS) + with self.saved_context(): + self.moveTo(x_end, y_end ,0) + self.hole(0, 0, 0.5) + + if not outerTestPoly.contains(line_this): + continue + x_start, y_start , x_end, y_end = line_this.bounds + #initialize walking x coordinate + xw = (math.ceil((x_start - xs) / (2 * max_radius_x + hspace)) * (2 * max_radius_x + hspace)) + xs + + # look up matching inner line + while (inner_line_index < len(inner_line_split) and + (inner_line_split.geoms[inner_line_index].bounds[2] < xw + or not innerTestPoly.contains(inner_line_split.geoms[inner_line_index]))): + inner_line_index += 1 + + # and process line + while not xw > x_end: + # are we in inner polygone already? + if (len(inner_line_split) > inner_line_index and + xw > inner_line_split.geoms[inner_line_index].bounds[0]): + # place inner, full size polygons + while xw < inner_line_split.geoms[inner_line_index].bounds[2]: + self.regularPolygonHole(xw, y, r=max_radius, n=n, a=a) + xw += (2 * max_radius_x + hspace) + # forward to next inner line + while (inner_line_index < len(inner_line_split) and + (inner_line_split.geoms[inner_line_index].bounds[0] < xw + or not innerTestPoly.contains(inner_line_split.geoms[inner_line_index]))): + inner_line_index += 1 + if xw > x_end: + break + + # Check distance to border to size the polygon + pt = Point(xw, y) + r = min(borderPoly.exterior.distance(pt) - bspace, + max_radius) + # if too small, dismiss + if r >= min_radius: + self.regularPolygonHole(xw, y, r=r, n=n, a=a) + xw += (2 * max_radius_x + hspace) + + row += 1 + if pattern == "square": + y += 2 * max_radius_y + hspace - 0.0001 + else: + y += (math.sqrt(3) / 2 * (2 * max_radius_y + hspace)) - 0.0001 + + elif pattern == "hbar": + # 'optimum' hole size to be used + max_radius = max_radius_y + # check if at least one bar fits + if (max_y - min_y) < (2 * max_radius + 2 * bspace): + return + + #shrink original polygon + shrinkPoly = borderPoly.buffer(-1 * (bspace + max_radius - 0.01), join_style=2) + cutPoly = borderPoly.buffer(-1 * (bspace + max_radius - 0.000001), join_style=2) + + if self.debug: + self.showBorderPoly(list(shrinkPoly.exterior.coords)) + + segment_length = [bar_length / 2, bar_length] + segment_max = 1 + segment_toggle = False + + # set startpoint + y = min_y + bspace + max_radius + # and calc step width + step_y = 2 * max_radius_y + hspace - 0.0001 + + while y < (max_y - bspace - max_radius): + # toggle segment length each new line + if segment_toggle: + segment_max = 0 + segment_toggle ^= 1 + + # create line from left to right and cut according to shrinked polygon + line_complete = LineString([(min_x - 1, y), (max_x + 1, y)]) + line_split = split(line_complete, cutPoly) + + # process each line + for line_this in line_split.geoms: + + if self.debug and False: # enable to debug missing lines + x_start, y_start , x_end, y_end = line_this.bounds + with self.saved_context(): + self.moveTo(x_start, y_start ,0) + self.hole(0, 0, 0.5) + self.edge(x_end - x_start) + with self.saved_context(): + self.moveTo(x_start, y_start ,0) + self.text(str(shrinkPoly.contains(line_this)), 0, 0, fontsize=2, color=Color.ANNOTATIONS) + with self.saved_context(): + self.moveTo(x_end, y_end ,0) + self.hole(0, 0, 0.5) + + if shrinkPoly.contains(line_this): + # long segment are cut down further + if line_this.length > segment_length[segment_max]: + line_working = line_this + length = line_working.length + while length > 0: + x_start, y_start , xw_end, yw_end = line_working.bounds + # calculate point with required distance from start point + p = line_working.interpolate(segment_length[segment_max]) + # and use its coordinates as endpoint for this segment + x_end = p.x + y_end = p.y + # draw segment + self.set_source_color(Color.INNER_CUT) + with self.saved_context(): + self.moveTo(x_start, y_start + max_radius,0) + self.edge(x_end - x_start) + self.corner(-180, max_radius) + self.edge(x_end - x_start) + self.corner(-180, max_radius) + + if self.debug and False: # enable to debug cutting lines + self.set_source_color(Color.ANNOTATIONS) + with self.saved_context(): + self.moveTo(x_start, y_start, 0) + self.edge(x_end - x_start) + + s = "long - y: " + str(round(y, 1)) + " xs: " + str(round(x_start, 1)) + " xe: " + str(round(x_end, 1)) + " l: " + str(round(length, 1)) + " max: " + str(round(segment_length[segment_max], 1)) + with self.saved_context(): + self.text(s, x_start, y_start, fontsize=2, color=Color.ANNOTATIONS) + + # subtract length of segmant from total segment length + length -= (x_end - x_start + hspace + 2 * max_radius) + # create remaining line to work with + line_working = LineString([(x_end + hspace + 2 * max_radius, y_end), (xw_end, yw_end)]) + # next segment shall be long + segment_max = 1 + else: + # short segment can be drawn instantly + x_start, y_start , x_end, y_end = line_this.bounds + self.set_source_color(Color.INNER_CUT) + with self.saved_context(): + self.moveTo(x_start, y_start + max_radius, 0) + self.edge(x_end - x_start) + self.corner(-180, max_radius) + self.edge(x_end - x_start) + self.corner(-180, max_radius) + + if self.debug and False: # enable to debug short lines + self.set_source_color(Color.ANNOTATIONS) + with self.saved_context(): + self.moveTo(x_start, y_start, 0) + self.edge(x_end - x_start) + + s = "short - y: " + str(round(y, 1)) + " xs: " + str(round(x_start, 1)) + " xe: " + str(round(x_end, 1)) + " l: " + str(round(line_this.length, 1)) + " max: " + str(round(segment_length[segment_max], 1)) + with self.saved_context(): + self.text(s, x_start, y_start, fontsize=2, color=Color.ANNOTATIONS) + + segment_max = 1 + # short segment shall be skipped if a short segment shall start the line + if segment_toggle: + segment_max = 0 + y += step_y + else: + raise ValueError("fillHoles - unknown hole pattern: %s)" % pattern)
+ +
[docs] def hexHolesRectangle(self, x, y, settings=None, skip=None): + """Fills a rectangle with holes in a hex pattern. + + Settings have: + r : radius of holes + b : space between holes + style : what types of holes (not yet implemented) + + :param x: width + :param y: height + :param settings: (Default value = None) + :param skip: (Default value = None) function to check if hole should be present + gets x, y, r, b, posx, posy + + + """ + + if settings is None: + settings = self.hexHolesSettings + r, b, style = settings.diameter/2, settings.distance, settings.style + + w = r + b / 2.0 + dist = w * math.cos(math.pi / 6.0) + + # how many half circles do fit + cx = int((x - 2 * r) // (w)) + 2 + cy = int((y - 2 * r) // (dist)) + 2 + + # what's left on the sides + lx = (x - (2 * r + (cx - 2) * w)) / 2.0 + ly = (y - (2 * r + ((cy // 2) * 2) * dist - 2 * dist)) / 2.0 + + for i in range(cy // 2): + for j in range((cx - (i % 2)) // 2): + px = 2 * j * w + r + lx + py = i * 2 * dist + r + ly + if i % 2: + px += w + if skip and skip(x, y, r, b, px, py): + continue + self.hole(px, py, r=r)
+ + def __skipcircle(self, x, y, r, b, posx, posy): + cx, cy = x / 2.0, y / 2.0 + return (dist(posx - cx, posy - cy) > (cx - r)) + +
[docs] def hexHolesCircle(self, d, settings=None): + """ + Fill circle with holes in a hex pattern + + :param d: diameter of the circle + :param settings: (Default value = None) + + """ + d2 = d / 2.0 + self.hexHolesRectangle(d, d, settings=settings, skip=self.__skipcircle)
+ +
[docs] def hexHolesPlate(self, x, y, rc, settings=None): + """ + Fill a plate with holes in a hex pattern + + :param x: width + :param y: height + :param rc: radius of the corners + :param settings: (Default value = None) + + """ + + def skip(x, y, r, b, posx, posy): + """ + + :param x: + :param y: + :param r: + :param b: + :param posx: + :param posy: + + """ + posx = abs(posx - (x / 2.0)) + posy = abs(posy - (y / 2.0)) + + wx = 0.5 * x - rc - r + wy = 0.5 * y - rc - r + + if (posx <= wx) or (posy <= wx): + return 0 + return dist(posx - wx, posy - wy) > rc + + self.hexHolesRectangle(x, y, settings, skip=skip)
+ +
[docs] def hexHolesHex(self, h, settings=None, grow=None): + """ + Fill a hexagon with holes in a hex pattern + + :param h: height + :param settings: (Default value = None) + :param grow: (Default value = None) + + """ + if settings is None: + settings = self.hexHolesSettings + r, b, style = settings.diameter/2, settings.distance, settings.style + + self.ctx.rectangle(0, 0, h, h) + w = r + b / 2.0 + dist = w * math.cos(math.pi / 6.0) + cy = 2 * int((h - 4 * dist) // (4 * w)) + 1 + + leftover = h - 2 * r - (cy - 1) * 2 * r + if grow == 'space ': + b += leftover / (cy - 1) / 2 + + # recalulate with adjusted values + w = r + b / 2.0 + dist = w * math.cos(math.pi / 6.0) + + self.moveTo(h / 2.0 - (cy // 2) * 2 * w, h / 2.0) + for j in range(cy): + self.hole(2 * j * w, 0, r) + for i in range(1, cy / 2 + 1): + for j in range(cy - i): + self.hole(j * 2 * w + i * w, i * 2 * dist, r) + self.hole(j * 2 * w + i * w, -i * 2 * dist, r)
+ +
[docs] def flex2D(self, x, y, width=1): + """ + Fill a rectangle with a pattern allowing bending in both axis + + :param x: width + :param y: height + :param width: width between the lines of the pattern in multiples of thickness + """ + width *= self.thickness + cx = int(x // (5 * width)) + cy = int(y // (5 * width)) + + if cx == 0 or cy == 0: + return + + wx = x / 5. / cx + wy = y / 5. / cy + + armx = (4 * wx, 90, 4 * wy, 90, 2 * wx, 90, 2 * wy) + army = (4 * wy, 90, 4 * wx, 90, 2 * wy, 90, 2 * wx) + for i in range(cx): + for j in range(cy): + if (i + j) % 2: + with self.saved_context(): + self.moveTo((5 * i) * wx, (5 * j) * wy) + self.polyline(*armx) + with self.saved_context(): + self.moveTo((5 * i + 5) * wx, (5 * j + 5) * wy, -180) + self.polyline(*armx) + else: + with self.saved_context(): + self.moveTo((5 * i + 5) * wx, (5 * j) * wy, 90) + self.polyline(*army) + with self.saved_context(): + self.moveTo((5 * i) * wx, (5 * j + 5) * wy, -90) + self.polyline(*army) + self.ctx.stroke()
+ +
[docs] @restore + def fingerHoleRectangle(self, dx, dy, x=0., y=0., angle=0., outside=False): + """ + Place finger holes for four walls - attaching a box on this plane + + :param dx: size in x direction + :param dy: size in y direction + :param x: x position of the center + :param y: y position of the center + :param angle: angle in which the rectangle is placed + :param outside: meassure size from the outside of the walls - not the inside + """ + self.moveTo(x, y, angle) + d = 0.5*self.thickness + if outside: + d = -d + + self.fingerHolesAt(dx/2+d, -dy/2, dy, 90) + self.fingerHolesAt(-dx/2-d, -dy/2, dy, 90) + self.fingerHolesAt(-dx/2, -dy/2-d, dx, 0) + self.fingerHolesAt(-dx/2, dy/2+d, dx, 0)
+ + ################################################## + ### parts + ################################################## + + def _splitWall(self, pieces, side): + """helper for roundedPlate and surroundingWall + figures out what sides to split + """ + return [ + (False, False, False, False, True), + (True, False, False, False, True), + (True, False, True, False, True), + (True, True, True, False, True), + (True, True, True, True, True), + ][pieces][side] + +
[docs] def roundedPlate(self, x, y, r, edge="f", callback=None, + holesMargin=None, holesSettings=None, + bedBolts=None, bedBoltSettings=None, + wallpieces=1, + extend_corners=True, + move=None): + """Plate with rounded corner fitting to .surroundingWall() + + For the callbacks the sides are counted depending on wallpieces + + :param x: width + :param y: height + :param r: radius of the corners + :param callback: (Default value = None) + :param holesMargin: (Default value = None) set to get hex holes + :param holesSettings: (Default value = None) + :param bedBolts: (Default value = None) + :param bedBoltSettings: (Default value = None) + :param wallpieces: (Default value = 1) # of separate surrounding walls + :param extend_corners: (Default value = True) have corners outset with the edges + :param move: (Default value = None) + + """ + corner_holes = True + + t = self.thickness + edge = self.edges.get(edge, edge) + overallwidth = x + 2 * edge.spacing() + overallheight = y + 2 * edge.spacing() + + if self.move(overallwidth, overallheight, move, before=True): + return + + lx = x - 2*r + ly = y - 2*r + + self.moveTo(edge.spacing(), + edge.margin()) + self.moveTo(r, 0) + + if wallpieces > 4: + wallpieces = 4 + + wallcount = 0 + for nr, l in enumerate((lx, ly, lx, ly)): + if self._splitWall(wallpieces, nr): + for i in range(2): + self.cc(callback, wallcount, y=edge.startwidth()+self.burn) + edge(l / 2.0 , + bedBolts=self.getEntry(bedBolts, wallcount), + bedBoltSettings=self.getEntry(bedBoltSettings, wallcount)) + wallcount += 1 + else: + self.cc(callback, wallcount, y=edge.startwidth()+self.burn) + edge(l, + bedBolts=self.getEntry(bedBolts, wallcount), + bedBoltSettings=self.getEntry(bedBoltSettings, wallcount)) + wallcount += 1 + if extend_corners: + if corner_holes: + with self.saved_context(): + self.moveTo(0, edge.startwidth()) + self.polyline(0, (90, r), 0, -90, t, -90, 0, + (-90, r+t), 0, -90, t, -90, 0,) + self.ctx.stroke() + self.corner(90, r + edge.startwidth()) + else: + self.step(-edge.endwidth()) + self.corner(90, r) + self.step(edge.startwidth()) + + self.ctx.restore() + self.ctx.save() + + self.moveTo(edge.margin(), + edge.margin()) + + if holesMargin is not None: + self.moveTo(holesMargin, holesMargin) + if r > holesMargin: + r -= holesMargin + else: + r = 0 + self.hexHolesPlate(x - 2 * holesMargin, y - 2 * holesMargin, r, + settings=holesSettings) + + self.move(overallwidth, overallheight, move)
+ +
[docs] def surroundingWallPiece(self, cbnr, x, y, r, pieces=1): + """ + Return the geometry of a pices of surroundingWall with the given + callback number. + :param cbnr: number of the callback corresponding to this part of the wall + :param x: width of matching roundedPlate + :param y: height of matching roundedPlate + :param r: corner radius of matching roundedPlate + :param pieces: (Default value = 1) number of separate pieces + :return: (left, length, right) left and right are Booleans that are True if the start or end of the wall is on that side. + """ + if pieces<=2 and (y - 2 * r) < 1E-3: + # remove zero length y sides + sides = (x/2-r, x - 2*r, x - 2*r) + if pieces > 0: # hack to get the right splits + pieces += 1 + else: + sides = (x/2-r, y - 2*r, x - 2*r, y - 2*r, x - 2*r) + + wallcount = 0 + for nr, l in enumerate(sides): + if self._splitWall(pieces, nr) and nr > 0: + if wallcount == cbnr: + return (False, l/2, True) + wallcount += 1 + if wallcount == cbnr: + return (True, l/2, False) + wallcount += 1 + else: + if wallcount == cbnr: + return (False, l, False) + wallcount += 1 + return (False, 0.0, False)
+ +
[docs] def surroundingWall(self, x, y, r, h, + bottom='e', top='e', + left="D", right="d", + pieces=1, + extend_corners=True, + callback=None, + move=None): + """ + Wall(s) with flex fiting around a roundedPlate() + + For the callbacks the sides are counted depending on pieces + + :param x: width of matching roundedPlate + :param y: height of matching roundedPlate + :param r: corner radius of matching roundedPlate + :param h: inner height of the wall (without edges) + :param bottom: (Default value = 'e') Edge type + :param top: (Default value = 'e') Edge type + :param left: (Default value = 'D') left edge(s) + :param right: (Default value = 'd') right edge(s) + :param pieces: (Default value = 1) number of separate pieces + :param callback: (Default value = None) + :param move: (Default value = None) + + """ + t = self.thickness + c4 = (r + self.burn) * math.pi * 0.5 # circumference of quarter circle + c4 = c4 / self.edges["X"].settings.stretch + + top = self.edges.get(top, top) + bottom = self.edges.get(bottom, bottom) + left = self.edges.get(left, left) + right = self.edges.get(right, right) + + # XXX assumes startwidth == endwidth + if extend_corners: + topwidth = t + bottomwidth = t + else: + topwidth = top.startwidth() + bottomwidth = bottom.startwidth() + + overallwidth = 2*x + 2*y - 8*r + 4*c4 + (self.edges["d"].spacing() + self.edges["D"].spacing() + self.spacing) * pieces + overallheight = h + max(t, top.spacing()) + max(t, bottom.spacing()) + + if self.move(overallwidth, overallheight, move, before=True): + return + + self.moveTo(left.spacing(), bottom.margin()) + + wallcount = 0 + tops = [] # edges needed on the top for this wall segment + + if pieces<=2 and (y - 2 * r) < 1E-3: + # remove zero length y sides + c4 *= 2 + sides = (x/2-r, x - 2*r, x - 2*r) + if pieces > 0: # hack to get the right splits + pieces += 1 + else: + sides = (x/2-r, y - 2*r, x - 2*r, y - 2*r, x - 2*r) + + for nr, l in enumerate(sides): + if self._splitWall(pieces, nr) and nr > 0: + self.cc(callback, wallcount, y=bottomwidth + self.burn) + wallcount += 1 + bottom(l / 2.) + tops.append(l / 2.) + + # complete wall segment + with self.saved_context(): + self.edgeCorner(bottom, right, 90) + right(h) + self.edgeCorner(right, top, 90) + for n, d in enumerate(reversed(tops)): + if n % 2: # flex + self.step(topwidth-top.endwidth()) + self.edge(d) + self.step(top.startwidth()-topwidth) + else: + top(d) + self.edgeCorner(top, left, 90) + left(h) + self.edgeCorner(left, bottom, 90) + + if nr == len(sides) - 1: + break + # start new wall segment + tops = [] + self.moveTo(right.margin() + left.margin() + self.spacing) + self.cc(callback, wallcount, y=bottomwidth + self.burn) + wallcount += 1 + bottom(l / 2.) + tops.append(l / 2.) + else: + self.cc(callback, wallcount, y=bottomwidth + self.burn) + wallcount += 1 + bottom(l) + tops.append(l) + self.step(bottomwidth-bottom.endwidth()) + self.edges["X"](c4, h + topwidth + bottomwidth) + self.step(bottom.startwidth()-bottomwidth) + tops.append(c4) + + self.move(overallwidth, overallheight, move)
+ +
[docs] def rectangularWall(self, x, y, edges="eeee", + ignore_widths=[], + holesMargin=None, holesSettings=None, + bedBolts=None, bedBoltSettings=None, + callback=None, + move=None, + label=""): + """ + Rectangular wall for all kind of box like objects + + :param x: width + :param y: height + :param edges: (Default value = "eeee") bottom, right, top, left + :param ignore_widths: list of edge_widths added to adjacent edge + :param holesMargin: (Default value = None) + :param holesSettings: (Default value = None) + :param bedBolts: (Default value = None) + :param bedBoltSettings: (Default value = None) + :param callback: (Default value = None) + :param move: (Default value = None) + :param label: rendered to identify parts, it is not ment to be cut or etched (Default value = "") + + """ + if len(edges) != 4: + raise ValueError("four edges required") + edges = [self.edges.get(e, e) for e in edges] + edges += edges # append for wrapping around + overallwidth = x + edges[-1].spacing() + edges[1].spacing() + overallheight = y + edges[0].spacing() + edges[2].spacing() + + if self.move(overallwidth, overallheight, move, before=True): + return + + if 7 not in ignore_widths: + self.moveTo(edges[-1].spacing()) + self.moveTo(0, edges[0].margin()) + for i, l in enumerate((x, y, x, y)): + self.cc(callback, i, y=edges[i].startwidth() + self.burn) + e1, e2 = edges[i], edges[i + 1] + if (2*i-1 in ignore_widths or + 2*i-1+8 in ignore_widths): + l += edges[i-1].endwidth() + if 2*i in ignore_widths: + l += edges[i+1].startwidth() + e2 = self.edges["e"] + if 2*i+1 in ignore_widths: + e1 = self.edges["e"] + + edges[i](l, + bedBolts=self.getEntry(bedBolts, i), + bedBoltSettings=self.getEntry(bedBoltSettings, i)) + self.edgeCorner(e1, e2, 90) + + if holesMargin is not None: + self.moveTo(holesMargin, + holesMargin + edges[0].startwidth()) + self.hexHolesRectangle(x - 2 * holesMargin, y - 2 * holesMargin, settings=holesSettings) + + self.move(overallwidth, overallheight, move, label=label)
+ +
[docs] def flangedWall(self, x, y, edges="FFFF", flanges=None, r=0.0, + callback=None, move=None, label=""): + """Rectangular wall with flanges extending the regular size + + This is similar to the rectangularWall but it may extend to either + side. Sides with flanges may only have e, E, or F edges - the later + being replaced with fingerHoles. + + :param x: width + :param y: height + :param edges: (Default value = "FFFF") bottom, right, top, left + :param flanges: (Default value = None) list of width of the flanges + :param r: radius of the corners of the flange + :param callback: (Default value = None) + :param move: (Default value = None) + :param label: rendered to identify parts, it is not ment to be cut or etched (Default value = "") + """ + + t = self.thickness + + if not flanges: + flanges = [0.0] * 4 + + while len(flanges) < 4: + flanges.append(0.0) + + edges = [self.edges.get(e, e) for e in edges] + # double to allow looping around + edges = edges + edges + flanges = flanges + flanges + + tw = x + edges[1].spacing() + flanges[1] + edges[3].spacing() + flanges[3] + th = y + edges[0].spacing() + flanges[0] + edges[2].spacing() + flanges[2] + + if self.move(tw, th, move, True): + return + + rl = min(r, max(flanges[-1], flanges[0])) + self.moveTo(rl + edges[-1].margin(), edges[0].margin()) + + for i in range(4): + l = y if i % 2 else x + + rl = min(r, max(flanges[i-1], flanges[i])) + rr = min(r, max(flanges[i], flanges[i+1])) + self.cc(callback, i, x=-rl) + if flanges[i]: + if edges[i] is self.edges["F"] or edges[i] is self.edges["h"]: + self.fingerHolesAt(flanges[i-1]+t-rl, 0.5*t+flanges[i], l, + angle=0) + self.edge(l+flanges[i-1]+flanges[i+1]+edges[i-1].endwidth()+edges[i+1].startwidth()-rl-rr) + else: + self.edge(flanges[i-1]+edges[i-1].endwidth()-rl) + edges[i](l) + self.edge(flanges[i+1]+edges[i+1].startwidth()-rr) + self.corner(90, rr) + self.move(tw, th, move, label=label)
+ +
[docs] def rectangularTriangle(self, x, y, edges="eee", r=0.0, num=1, + bedBolts=None, bedBoltSettings=None, + callback=None, + move=None, + label=""): + """ + Rectangular triangular wall + + :param x: width + :param y: height + :param edges: (Default value = "eee") bottom, right[, diagonal] + :param r: radius towards the hypothenuse + :param num: (Default value = 1) number of triangles + :param bedBolts: (Default value = None) + :param bedBoltSettings: (Default value = None) + :param callback: (Default value = None) + :param move: (Default value = None) + :param label: rendered to identify parts, it is not ment to be cut or etched (Default value = "") + """ + edges = [self.edges.get(e, e) for e in edges] + if len(edges) == 2: + edges.append(self.edges["e"]) + if len(edges) != 3: + raise ValueError("two or three edges required") + + r = min(r, x, y) + a = math.atan2(y-r, float(x-r)) + alpha = math.degrees(a) + print(a, alpha) + if a > 0: + width = x + (edges[-1].spacing()+self.spacing)/math.sin(a) + edges[1].spacing() + self.spacing + else: + width = x + (edges[-1].spacing()+self.spacing) + edges[1].spacing() + self.spacing + height = y + edges[0].spacing() + edges[2].spacing() * math.cos(a) + 2* self.spacing + self.spacing + if num > 1: + width = 2*width - x + r - self.spacing + dx = width - x - edges[1].spacing() - self.spacing / 2 + dy = edges[0].spacing() + self.spacing / 2 + + overallwidth = width * (num // 2 + num % 2) - self.spacing + overallheight = height - self.spacing + + if self.move(overallwidth, overallheight, move, before=True): + return + + if self.debug: + self.rectangularHole(width/2., height/2., width, height) + + self.moveTo(dx - self.spacing / 2, dy - self.spacing / 2) + + for n in range(num): + for i, l in enumerate((x, y)): + self.cc(callback, i, y=edges[i].startwidth() + self.burn) + edges[i](l, + bedBolts=self.getEntry(bedBolts, i), + bedBoltSettings=self.getEntry(bedBoltSettings, i)) + if i==0: + self.edgeCorner(edges[i], edges[i + 1], 90) + self.edgeCorner(edges[i], "e", 90) + + self.corner(alpha, r) + self.cc(callback, 2) + self.step(edges[2].startwidth()) + edges[2](((x-r)**2+(y-r)**2)**0.5) + self.step(-edges[2].endwidth()) + self.corner(90-alpha, r) + self.corner(90) + self.ctx.stroke() + + self.moveTo(width-2*dx, height - 2*dy, 180) + if n % 2: + self.moveTo(width) + + self.move(overallwidth, overallheight, move, label=label)
+ +
[docs] def trapezoidWall(self, w, h0, h1, edges="eeee", + callback=None, move=None, + label=""): + """ + Rectangular trapezoidal wall + + :param w: width + :param h0: left height + :param h1: right height + :param edges: (Default value = "eee") bottom, right, left + :param callback: (Default value = None) + :param move: (Default value = None) + :param label: rendered to identify parts, it is not ment to be cut or etched (Default value = "") + """ + + edges = [self.edges.get(e, e) for e in edges] + + overallwidth = w + edges[-1].spacing() + edges[1].spacing() + overallheight = max(h0, h1) + edges[0].spacing() + + if self.move(overallwidth, overallheight, move, before=True): + return + + a = math.degrees(math.atan((h1-h0)/w)) + l = ((h0-h1)**2+w**2)**0.5 + + self.moveTo(edges[-1].spacing(), edges[0].margin()) + self.cc(callback, 0, y=edges[0].startwidth()) + edges[0](w) + self.edgeCorner(edges[0], edges[1], 90) + self.cc(callback, 1, y=edges[1].startwidth()) + edges[1](h1) + self.edgeCorner(edges[1], self.edges["e"], 90) + self.corner(a) + self.cc(callback, 2) + edges[2](l) + self.corner(-a) + self.edgeCorner(self.edges["e"], edges[-1], 90) + self.cc(callback, 3, y=edges[-1].startwidth()) + edges[3](h0) + self.edgeCorner(edges[-1], edges[0], 90) + + self.move(overallwidth, overallheight, move, label=label)
+ +
[docs] def trapezoidSideWall(self, w, h0, h1, edges="eeee", + radius=0.0, callback=None, move=None, + label=""): + """ + Rectangular trapezoidal wall + + :param w: width + :param h0: left height + :param h1: right height + :param edges: (Default value = "eeee") bottom, right, left + :param radius: (Default value = 0.0) radius of upper corners + :param callback: (Default value = None) + :param move: (Default value = None) + :param label: rendered to identify parts, it is not ment to be cut or etched (Default value = "") + """ + + edges = [self.edges.get(e, e) for e in edges] + + overallwidth = w + edges[-1].spacing() + edges[1].spacing() + overallheight = max(h0, h1) + edges[0].spacing() + + if self.move(overallwidth, overallheight, move, before=True): + return + + r = min(radius, abs(h0-h1)) + ws = w-r + if h0 > h1: + ws += edges[1].endwidth() + else: + ws += edges[3].startwidth() + hs = abs(h1-h0) - r + a = math.degrees(math.atan(hs/ws)) + l = (ws**2+hs**2)**0.5 + + self.moveTo(edges[-1].spacing(), edges[0].margin()) + self.cc(callback, 0, y=edges[0].startwidth()) + edges[0](w) + self.edgeCorner(edges[0], edges[1], 90) + self.cc(callback, 1, y=edges[1].startwidth()) + edges[1](h1) + + if h0 > h1: + self.polyline(0, (90-a, r)) + self.cc(callback, 2) + edges[2](l) + self.polyline(0, (a, r), edges[3].startwidth(), 90) + else: + self.polyline(0, 90, edges[1].endwidth(), (a, r)) + self.cc(callback, 2) + edges[2](l) + self.polyline(0, (90-a, r)) + self.cc(callback, 3, y=edges[-1].startwidth()) + edges[3](h0) + self.edgeCorner(edges[-1], edges[0], 90) + + self.move(overallwidth, overallheight, move, label)
+ + ### polygonWall and friends + + def _polygonWallExtend(self, borders, edge, close=False): + posx, posy = 0, 0 + ext = [ 0.0 ] * 4 + angle = 0 + + def checkpoint(ext, x, y): + ext[0] = min(ext[0], x) + ext[1] = min(ext[1], y) + ext[2] = max(ext[2], x) + ext[3] = max(ext[3], y) + + for i in range(len(borders)): + if i % 2: + try: + a, r = borders[i] + except TypeError: + angle = (angle + borders[i]) % 360 + continue + if a > 0: + centerx = posx + r * math.cos(math.radians(angle+90)) + centery = posy + r * math.sin(math.radians(angle+90)) + else: + centerx = posx + r * math.cos(math.radians(angle-90)) + centery = posy + r * math.sin(math.radians(angle-90)) + + for direction in (0, 90, 180, 270): + if (a > 0 and + angle <= direction and (angle + a) % 360 >= direction): + direction -= 90 + elif (a < 0 and + angle >= direction and (angle + a) % 360 <= direction): + direction -= 90 + else: + continue + checkpoint(ext, centerx + r * math.cos(math.radians(direction)), centery + r * math.sin(math.radians(direction))) + #print("%4s %4s %4s %f %f" % (angle, direction+90, angle+a, centerx + r * math.cos(math.radians(direction)), centery + r * math.sin(math.radians(direction)))) + angle = (angle + a) % 360 + if a > 0: + posx = centerx + r * math.cos(math.radians(angle-90)) + posy = centery + r * math.sin(math.radians(angle-90)) + else: + posx = centerx + r * math.cos(math.radians(angle+90)) + posy = centery + r * math.sin(math.radians(angle+90)) + else: + posx += borders[i] * math.cos(math.radians(angle)) + posy += borders[i] * math.sin(math.radians(angle)) + checkpoint(ext, posx, posy) + + ext[0] -= edge.margin() + ext[1] -= edge.margin() + ext[2] += edge.margin() + ext[3] += edge.margin() + + return ext + +
[docs] def polygonWall(self, borders, edge="f", turtle=False, + callback=None, move=None, label=""): + """ + Polygon wall for all kind of multi-edged objects + + :param borders: array of distance and angles to draw + :param edge: (Default value = "f") Edges to apply. If the array of borders contains more segments that edges, the edge will wrap. Only edge types without start and end width suppported for now. + :param turtle: (Default value = False) + :param callback: (Default value = None) + :param move: (Default value = None) + :param label: rendered to identify parts, it is not ment to be cut or etched (Default value = "") + + """ + try: + edges = [self.edges.get(e, e) for e in edge] + except TypeError: + edges = [self.edges.get(edge, edge)] + + t = self.thickness # XXX edge.margin() + + minx, miny, maxx, maxy = self._polygonWallExtend(borders, edges[0]) + + tw, th = maxx - minx, maxy - miny + + if not turtle: + if self.move(tw, th, move, True): + return + + self.moveTo(-minx, -miny) + + length_correction = 0. + for i in range(0, len(borders), 2): + self.cc(callback, i) + self.edge(length_correction) + l = borders[i] - length_correction + next_angle = borders[i+1] + + if isinstance(next_angle, (int, float)) and next_angle < 0: + length_correction = t * math.tan(math.radians((-next_angle / 2))) + else: + length_correction = 0.0 + l -= length_correction + edge = edges[(i//2)%len(edges)] + edge(l) + self.edge(length_correction) + self.corner(next_angle, tabs=1) + + if not turtle: + self.move(tw, th, move, label=label)
+ +
[docs] @restore + def polygonWalls(self, borders, h, bottom="F", top="F", symetrical=True): + bottom = self.edges.get(bottom, bottom) + top = self.edges.get(top, top) + t = self.thickness # XXX edge.margin() + + leftsettings = copy.deepcopy(self.edges["f"].settings) + lf, lF, lh = leftsettings.edgeObjects(self, add=False) + rightsettings = copy.deepcopy(self.edges["f"].settings) + rf, rF, rh = rightsettings.edgeObjects(self, add=False) + + length_correction = 0. + angle = borders[-1] + i = 0 + part_cnt = 0 + + self.moveTo(0, bottom.margin()) + while i < len(borders): + if symetrical: + if part_cnt % 2: + left, right = lf, rf + else: + # last part of an uneven lot + if (part_cnt == (len(borders)//2)-1): + left, right = lF, rf + else: + left, right = lF, rF + else: + left, right = lf, rF + + top_lengths = [] + top_edges = [] + + self.moveTo(left.spacing() + self.spacing, 0) + l = borders[i] - length_correction + leftsettings.setValues(self.thickness, angle=angle) + angle = borders[i+1] + + while isinstance(angle, (tuple, list)): + bottom(l) + angle, radius = angle + lr = abs(math.radians(angle) * radius) + self.edges["X"](lr, h + 2*t) # XXX + top_lengths.append(l) + top_lengths.append(lr) + top_edges.append(top) + top_edges.append("E") + + i += 2 + l = borders[i] + angle = borders[i+1] + + rightsettings.setValues(self.thickness, angle=angle) + if angle < 0: + length_correction = t * math.tan(math.radians((-angle / 2))) + else: + length_correction = 0.0 + l -= length_correction + + bottom(l) + + top_lengths.append(l) + top_edges.append(top) + with self.saved_context(): + self.edgeCorner(bottom, right, 90) + right(h) + self.edgeCorner(right, top, 90) + + top_edges.reverse() + top_lengths.reverse() + edges.CompoundEdge(self, top_edges, top_lengths)(sum(top_lengths)) + self.edgeCorner(top, left, 90) + left(h) + self.edgeCorner(left, bottom, 90) + self.ctx.stroke() + + self.moveTo(right.spacing() + self.spacing) + part_cnt += 1 + i += 2
+ + + ################################################## + ### Place Parts + ################################################## + +
[docs] def partsMatrix(self, n, width, move, part, *l, **kw): + """place many of the same part + + :param n: number of parts + :param width: number of parts in a row (0 for same as n) + :param move: (Default value = None) + :param part: callable that draws a part and knows move param + :param \*l: params for part + :param \*\*kw: keyword params for part + """ + if n <= 0: + return + + if not width: + width = n + + rows = n//width + (1 if n % width else 0) + + if not move: + move = "" + move = move.split() + + #move down / left before + for m in move: + if m == "left": + kw["move"] = "left only" + for i in range(width): + part(*l, **kw) + if m == "down": + kw["move"] = "down only" + for i in range(rows): + part(*l, **kw) + # draw matrix + for i in range(rows): + with self.saved_context(): + for j in range(width): + if "only" in move: + break + if width*i+j >= n: + break + kw["move"] = "right" + part(*l, **kw) + kw["move"] = "up only" + part(*l, **kw) + + # Move back down + if "up" not in move: + kw["move"] = "down only" + for i in range(rows): + part(*l, **kw) + + # Move right + if "right" in move: + kw["move"] = "right only" + for i in range(width): + part(*l, **kw)
+ +
[docs] def mirrorX(self, f, offset=0.0): + """Wrap a function to draw mirrored at the y axis + + :param f: function to wrap + :param offset: (default value = 0.0) axis to mirror at + """ + def r(): + self.moveTo(offset, 0) + with self.saved_context(): + self.ctx.scale(-1, 1) + f() + return r
+ +
[docs] def mirrorY(self, f, offset=0.0): + """Wrap a function to draw mirrored at the x axis + + :param f: function to wrap + :param offset: (default value = 0.0) axis to mirror at + """ + def r(): + self.moveTo(0, offset) + with self.saved_context(): + self.ctx.scale(1, -1) + f() + return r
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/Color.html b/html/_modules/boxes/Color.html new file mode 100644 index 0000000..0649952 --- /dev/null +++ b/html/_modules/boxes/Color.html @@ -0,0 +1,104 @@ + + + + + + + + boxes.Color — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.Color

+
[docs]class Color: + BLACK = [ 0.0, 0.0, 0.0 ] + BLUE = [ 0.0, 0.0, 1.0 ] + GREEN = [ 0.0, 1.0, 0.0 ] + RED = [ 1.0, 0.0, 0.0 ] + CYAN = [ 0.0, 1.0, 1.0 ] + YELLOW = [ 1.0, 1.0, 0.0 ] + MAGENTA = [ 1.0, 0.0, 1.0 ] + WHITE = [ 1.0, 1.0, 1.0 ] + + # TODO: Make this configurable + OUTER_CUT = BLACK + INNER_CUT = BLUE + ANNOTATIONS = RED + ETCHING = GREEN + ETCHING_DEEP = CYAN
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/edges.html b/html/_modules/boxes/edges.html new file mode 100644 index 0000000..93015bd --- /dev/null +++ b/html/_modules/boxes/edges.html @@ -0,0 +1,2717 @@ + + + + + + + + boxes.edges — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.edges

+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import math
+import inspect
+import argparse
+import re
+import abc
+
+from boxes import gears
+
+
[docs]def argparseSections(s): + """ + Parse sections parameter + + :param s: string to parse + + """ + + result = [] + + s = re.split(r"\s|:", s) + + try: + for part in s: + m = re.match(r"^(\d+(\.\d+)?)/(\d+)$", part) + if m: + n = int(m.group(3)) + result.extend([float(m.group(1)) / n] * n) + continue + m = re.match(r"^(\d+(\.\d+)?)\*(\d+)$", part) + if m: + n = int(m.group(3)) + result.extend([float(m.group(1))] * n) + continue + result.append(float(part)) + except ValueError: + raise argparse.ArgumentTypeError("Don't understand sections string") + + if not result: + result.append(0.0) + + return result
+ +
[docs]def getDescriptions(): + d = {edge.char: edge.description for edge in globals().values() + if inspect.isclass(edge) and issubclass(edge, BaseEdge) + and edge.char} + d['j'] = d['i'] + " (other end)" + d['J'] = d['I'] + " (other end)" + d['k'] = d['i'] + " (both ends)" + d['K'] = d['I'] + " (both ends)" + d['O'] = d['o'] + ' (other end)' + d['P'] = d['p'] + ' (other end)' + d['U'] = d['u'] + ' top side' + d['v'] = d['u'] + u' for 90° lid' + d['V'] = d['u'] + u' 90° lid' + return d
+ + +
[docs]class BoltPolicy(object): + """Abstract class + + Distributes (bed) bolts on a number of segments + (fingers of a finger joint) + + """ + +
[docs] def drawbolt(self, pos): + """Add a bolt to this segment? + + :param pos: number of the finger + + """ + return False
+ +
[docs] def numFingers(self, numfingers): + """Return next smaller, possible number of fingers + + :param numfingers: number of fingers to aim for + + """ + return numfingers
+ + def _even(self, numFingers): + """ + Return same or next smaller even number + + :param numFingers: + + """ + return (numFingers // 2) * 2 + + def _odd(self, numFingers): + """ + Return same or next smaller odd number + + :param numFingers: + + """ + if numFingers % 2: + return numFingers + else: + return numFingers - 1
+ + +
[docs]class Bolts(BoltPolicy): + """Distribute a fixed number of bolts evenly""" + + def __init__(self, bolts=1): + self.bolts = bolts + +
[docs] def numFingers(self, numFingers): + if self.bolts % 2: + self.fingers = self._even(numFingers) + else: + self.fingers = numFingers + + return self.fingers
+ +
[docs] def drawBolt(self, pos): + """ + Return if this finger needs a bolt + + :param pos: number of this finger + + """ + if pos > self.fingers // 2: + pos = self.fingers - pos + + if pos == 0: + return False + + if pos == self.fingers // 2 and not (self.bolts % 2): + return False + + return (math.floor((float(pos) * (self.bolts + 1) / self.fingers) - 0.01) != + math.floor((float(pos + 1) * (self.bolts + 1) / self.fingers) - 0.01))
+ + +############################################################################# +### Settings +############################################################################# + +
[docs]class Settings(object): + """Generic Settings class + + Used by different other classes to store measurements and details. + Supports absolute values and settings that grow with the thickness + of the material used. + + Overload the absolute_params and relative_params class attributes with + the suported keys and default values. The values are available via + attribute access. + """ + absolute_params = {} + relative_params = {} + +
[docs] @classmethod + def parserArguments(cls, parser, prefix=None, **defaults): + prefix = prefix or cls.__name__[:-len("Settings")] + + lines = cls.__doc__.split("\n") + + # Parse doc string + descriptions = {} + r = re.compile(r"^ +\* +(\S+) +: .* : +(.*)") + for l in lines: + m = r.search(l) + if m: + descriptions[m.group(1)] = m.group(2) + + group = parser.add_argument_group(lines[0] or lines[1]) + group.prefix = prefix + for name, default in (sorted(cls.absolute_params.items()) + + sorted(cls.relative_params.items())): + # Handle choices + choices = None + if isinstance(default, tuple): + choices = default + t = type(default[0]) + for val in default: + if (type(val) is not t or + type(val) not in (bool, int, float, str)): + raise ValueError("Type not supported: %r", val) + default = default[0] + + # Overwrite default + if name in defaults: + default = type(default)(defaults[name]) + + if type(default) not in (bool, int, float, str): + raise ValueError("Type not supported: %r", default) + if type(default) is bool: + from boxes import BoolArg + t = BoolArg() + else: + t = type(default) + + group.add_argument("--%s_%s" % (prefix, name), + type=t, + action="store", default=default, + choices=choices, + help=descriptions.get(name))
+ + def __init__(self, thickness, relative=True, **kw): + self.values = {} + for name, value in self.absolute_params.items(): + if isinstance(value, tuple): + value = value[0] + if type(value) not in (bool, int, float, str): + raise ValueError("Type not supported: %r", value) + self.values[name] = value + + self.thickness = thickness + factor = 1.0 + if relative: + factor = thickness + for name, value in self.relative_params.items(): + self.values[name] = value * factor + self.setValues(thickness, relative, **kw) + +
[docs] def edgeObjects(self, boxes, chars="", add=True): + """ + Generate Edge objects using this kind of settings + + :param boxes: Boxes object + :param chars: sequence of chars to be used by Edge objects + :param add: add the resulting Edge objects to the Boxes object's edges + + """ + edges = [] + return self._edgeObjects(edges, boxes, chars, add)
+ + def _edgeObjects(self, edges, boxes, chars, add): + for i, edge in enumerate(edges): + try: + char = chars[i] + edge.char = char + except IndexError: + pass + except TypeError: + pass + if add: + boxes.addParts(edges) + return edges + +
[docs] def setValues(self, thickness, relative=True, **kw): + """ + Set values + + :param thickness: thickness of the material used + :param relative: (Default value = True) Do scale by thickness + :param \*\*kw: parameters to set + + """ + factor = 1.0 + if relative: + factor = thickness + for name, value in kw.items(): + if name in self.absolute_params: + self.values[name] = value + elif name in self.relative_params: + self.values[name] = value * factor + else: + raise ValueError("Unknown parameter for %s: %s" % ( + self.__class__.__name__, name)) + self.checkValues()
+ +
[docs] def checkValues(self): + """ + Check if all values are in the right range. Raise ValueError if needed + """ + return
+ + def __getattr__(self, name): + if "values" in self.__dict__ and name in self.values: + return self.values[name] + raise AttributeError
+ +############################################################################# +### Edges +############################################################################# + + +
[docs]class BaseEdge(object): + """Abstract base class for all Edges""" + char = None + description = "Abstract Edge Class" + + def __init__(self, boxes, settings): + self.boxes = boxes + self.ctx = boxes.ctx + self.settings = settings + + def __getattr__(self, name): + """Hack for using unalter code form Boxes class""" + return getattr(self.boxes, name) + +
[docs] @abc.abstractmethod + def __call__(self, length, **kw): + pass
+ +
[docs] def startwidth(self): + """Amount of space the beginning of the edge is set below the inner space of the part """ + return 0.0
+ +
[docs] def endwidth(self): + return self.startwidth()
+ +
[docs] def margin(self): + """Space needed right of the starting point""" + return 0.0
+ +
[docs] def spacing(self): + """Space the edge needs outside of the inner space of the part""" + return self.startwidth() + self.margin()
+ +
[docs] def startAngle(self): + """Not yet supported""" + return 0.0
+ +
[docs] def endAngle(self): + """Not yet supported""" + return 0.0
+ + +
[docs]class Edge(BaseEdge): + """Straight edge""" + char = 'e' + description = "Straight Edge" + positive = False + + def __call__(self, length, bedBolts=None, bedBoltSettings=None, **kw): + """Draw edge of length mm""" + if bedBolts: + # distribute the bolts aequidistantly + interval_length = length / bedBolts.bolts + if self.positive: + d = (bedBoltSettings or self.bedBoltSettings)[0] + for i in range(bedBolts.bolts): + self.hole(0.5 * interval_length, + 0.5 * self.thickness, 0.5 * d) + self.edge(interval_length, tabs= + (i == 0 or i == bedBolts.bolts - 1)) + else: + for i in range(bedBolts.bolts): + self.bedBoltHole(interval_length, bedBoltSettings, tabs= + (i == 0 or i == bedBolts.bolts - 1)) + else: + self.edge(length, tabs=2)
+ + +
[docs]class OutSetEdge(Edge): + """Straight edge out set by one thickness""" + char = 'E' + description = "Straight Edge (outset by thickness)" + positive = True + +
[docs] def startwidth(self): + return self.boxes.thickness
+ +############################################################################# +#### MountingEdge +############################################################################# + +
[docs]class MountingSettings(Settings): + """Settings for Mounting Edge +Values: +* absolute_params + + * style : "straight edge, within" : edge style + * side : "back" : side of box (not all valid configurations make sense...) + * num : 2 : number of mounting holes (integer) + * margin : 0.125 : minimum space left and right without holes (fraction of the edge length) + * d_shaft : 3.0 : shaft diameter of mounting screw (in mm) + * d_head : 6.5 : head diameter of mounting screw (in mm) +""" + + PARAM_IN = "straight edge, within" + PARAM_EXT = "straight edge, extended" + PARAM_TAB = "mounting tab" + + PARAM_LEFT = "left" + PARAM_BACK = "back" + PARAM_RIGHT = "right" + PARAM_FRONT = "front" + + absolute_params = { + "style": (PARAM_IN, PARAM_EXT, PARAM_TAB), + "side": (PARAM_BACK, PARAM_LEFT, PARAM_RIGHT, PARAM_FRONT), + "num": 2, + "margin": 0.125, + "d_shaft" : 3.0, + "d_head" : 6.5 + } + +
[docs] def edgeObjects(self, boxes, chars="G", add=True): + edges = [MountingEdge(boxes, self)] + return self._edgeObjects(edges, boxes, chars, add)
+ + +
[docs]class MountingEdge(BaseEdge): + description = """Edge with pear shaped mounting holes""" # for slide-on mounting using flat-head screws""" + char = 'G' + +
[docs] def margin(self): + if self.settings.style == MountingSettings.PARAM_TAB: + return 2.75 * self.boxes.thickness + self.settings.d_head + else: + return 0
+ +
[docs] def startwidth(self): + if self.settings.style == MountingSettings.PARAM_EXT: + return 2.5 * self.boxes.thickness + self.settings.d_head + else: + return 0
+ + def __call__(self, length, **kw): + if length == 0.0: + return + + def check_bounds(val, mn, mx, name): + if not mn <= val <= mx: + raise ValueError(f"MountingEdge: {name} needs to be in [{mn}, {mx}] but is {val}") + + style = self.settings.style + margin = self.settings.margin + num = self.settings.num + ds = self.settings.d_shaft + dh = self.settings.d_head + if dh > 0: + width = 3 * self.thickness + dh + else: + width = ds + + if num != int(num): + raise ValueError(f"MountingEdge: num needs to be an integer number") + + check_bounds(margin, 0, 0.5, "margin") + if not dh == 0: + if not dh > ds: + raise ValueError(f"MountingEdge: d_shaft needs to be in 0 or > {ds}, but is {dh}") + + # Check how many holes fit + count = max(1, int(num)) + if count > 1: + margin_ = length * margin + gap = (length - 2 * margin_ - width*count) / (count - 1) + if gap < width: + count = int(((length - 2 * margin + width) / (2 * width)) - 0.5) + if count < 1: + self.edge(length) + return + if count < 2: + margin_ = (length - width) / 2 + gap = 0 + else: + gap = (length - 2 * margin_ - width*count) / (count - 1) + else: + margin_ = (length - width) / 2 + gap = 0 + + if style == MountingSettings.PARAM_TAB: + + # The edge until the first groove + self.edge(margin_, tabs=1) + + for i in range(count): + if i > 0: + self.edge(gap) + self.corner(-90,self.thickness/2) + self.edge(dh+1.5*ds-self.thickness/4-dh/2) + self.corner(90,self.thickness+dh/2) + self.corner(-90) + self.corner(90) + self.mountingHole(0,self.thickness*1.25+ds/2,ds,dh,-90) + self.corner(90,self.thickness+dh/2) + self.edge(dh+1.5*ds-self.thickness/4-dh/2) + self.corner(-90,self.thickness/2) + + # The edge until the end + self.edge(margin_, tabs=1) + else: + x = margin_ + for i in range(count): + x += width/2 + self.mountingHole(x,ds/2+self.thickness*1.5,ds,dh,-90) + x += width/2 + x += gap + self.edge(length)
+ + +############################################################################# +#### GroovedEdge +############################################################################# + +
[docs]class GroovedSettings(Settings): + """Settings for Grooved Edge +Values: + +* absolute_params + + * style : "arc" : the style of grooves + * tri_angle : 30 : the angle of triangular cuts + * arc_angle : 120 : the angle of arc cuts + * width : 0.2 : the width of each groove (fraction of the edge length) + * gap : 0.1 : the gap between grooves (fraction of the edge length) + * margin : 0.3 : minimum space left and right without grooves (fraction of the edge length) + * inverse : False : invert the groove directions + * interleave : False : alternate the direction of grooves +""" + + PARAM_ARC = "arc" + PARAM_FLAT = "flat" + PARAM_SOFTARC = "softarc" + PARAM_TRIANGLE = "triangle" + + absolute_params = { + "style": (PARAM_ARC, PARAM_FLAT, PARAM_TRIANGLE, PARAM_SOFTARC), + "tri_angle": 30, + "arc_angle": 120, + "width": 0.2, + "gap": 0.1, + "margin": 0.3, + "inverse": False, + "interleave": False, + } + +
[docs] def edgeObjects(self, boxes, chars="zZ", add=True): + edges = [GroovedEdge(boxes, self), + GroovedEdgeCounterPart(boxes, self)] + return self._edgeObjects(edges, boxes, chars, add)
+ + +
[docs]class GroovedEdgeBase(BaseEdge): +
[docs] def is_inverse(self): + return self.settings.inverse != self.inverse
+ + +
[docs] def groove_arc(self, width, angle=90, inv=-1.0): + side_length = width / math.sin(math.radians(angle)) / 2 + self.corner(inv * -angle) + self.corner(inv * angle, side_length) + self.corner(inv * angle, side_length) + self.corner(inv * -angle)
+ + +
[docs] def groove_soft_arc(self, width, angle=60, inv=-1.0): + side_length = width / math.sin(math.radians(angle)) / 4 + self.corner(inv * -angle, side_length) + self.corner(inv * angle, side_length) + self.corner(inv * angle, side_length) + self.corner(inv * -angle, side_length)
+ + +
[docs] def groove_triangle(self, width, angle=45, inv=-1.0): + side_length = width / math.cos(math.radians(angle)) / 2 + self.corner(inv * -angle) + self.edge(side_length) + self.corner(inv * 2 * angle) + self.edge(side_length) + self.corner(inv * -angle)
+ + + def __call__(self, length, **kw): + if length == 0.0: + return + + def check_bounds(val, mn, mx, name): + if not mn <= val <= mx: + raise ValueError(f"{name} needs to be in [{mn}, {mx}] but is {val}") + + style = self.settings.style + width = self.settings.width + margin = self.settings.margin + gap = self.settings.gap + interleave = self.settings.interleave + + check_bounds(width, 0, 1, "width") + check_bounds(margin, 0, 0.5, "margin") + check_bounds(gap, 0, 1, "gap") + + # Check how many grooves fit + count = max(0, int((1 - 2 * margin + gap) / (width + gap))) + inside_width = max(0, count * (width + gap) - gap) + margin = (1 - inside_width) / 2 + + # Convert to actual length + margin = length * margin + gap = length * gap + width = length * width + + # Determine the initial inversion + inv = 1 if self.is_inverse() else -1 + if interleave and self.inverse and count % 2 == 0: + inv = -inv + + # The edge until the first groove + self.edge(margin, tabs=1) + + # Grooves + for i in range(count): + if i > 0: + self.edge(gap) + if interleave: + inv = -inv + if style == GroovedSettings.PARAM_FLAT: + self.edge(width) + elif style == GroovedSettings.PARAM_ARC: + angle = self.settings.arc_angle / 2 + self.groove_arc(width, angle, inv) + elif style == GroovedSettings.PARAM_SOFTARC: + angle = self.settings.arc_angle / 2 + self.groove_soft_arc(width, angle, inv) + elif style == GroovedSettings.PARAM_TRIANGLE: + angle = self.settings.tri_angle + self.groove_triangle(width, angle, inv) + else: + raise ValueError("Unknown GroovedEdge style: %s)" % style) + + # The final edge + self.edge(margin, tabs=1)
+ + +
[docs]class GroovedEdge(GroovedEdgeBase): + description = """Edge with grooves""" + char = 'z' + inverse = False
+ + +
[docs]class GroovedEdgeCounterPart(GroovedEdgeBase): + description = """Edge with grooves (opposing side)""" + char = 'Z' + inverse = True
+ + +############################################################################# +#### Gripping Edge +############################################################################# + +
[docs]class GripSettings(Settings): + """Settings for GrippingEdge +Values: + +* absolute_params + + * style : "wave : "wave" or "bumps" + * outset : True : extend outward the straight edge + +* relative (in multiples of thickness) + + * depth : 0.3 : depth of the grooves + +""" + + absolute_params = { + "style": ("wave", "bumps"), + "outset": True, + } + + relative_params = { + "depth": 0.3, + } + +
[docs] def edgeObjects(self, boxes, chars="g", add=True): + edges = [GrippingEdge(boxes, self)] + return self._edgeObjects(edges, boxes, chars, add)
+ +
[docs]class GrippingEdge(BaseEdge): + description = """Corrugated edge useful as an gipping area""" + char = 'g' + +
[docs] def wave(self, length): + depth = self.settings.depth + grooves = int(length // (depth * 2.0)) + 1 + depth = length / grooves / 4.0 + + o = 1 if self.settings.outset else -1 + for groove in range(grooves): + self.corner(o * -90, depth) + self.corner(o * 180, depth) + self.corner(o * -90, depth)
+ +
[docs] def bumps(self, length): + depth = self.settings.depth + grooves = int(length // (depth * 2.0)) + 1 + depth = length / grooves / 2.0 + o = 1 if self.settings.outset else -1 + + if self.settings.outset: + self.corner(-90) + else: + self.corner(90) + self.edge(depth) + self.corner(-180) + + for groove in range(grooves): + self.corner(180, depth) + self.corner(-180, 0) + + if self.settings.outset: + self.corner(90) + else: + self.edge(depth) + self.corner(90)
+ +
[docs] def margin(self): + if self.settings.outset: + return self.settings.depth + else: + return 0.0
+ + def __call__(self, length, **kw): + if length == 0.0: + return + getattr(self, self.settings.style)(length)
+ + +
[docs]class CompoundEdge(BaseEdge): + """Edge composed of multiple different Edges""" + description = "Compound Edge" + + def __init__(self, boxes, types, lengths): + super(CompoundEdge, self).__init__(boxes, None) + + self.types = [self.edges.get(edge, edge) for edge in types] + self.lengths = lengths + self.length = sum(lengths) + +
[docs] def startwidth(self): + return self.types[0].startwidth()
+ +
[docs] def endwidth(self): + return self.types[-1].endwidth()
+ +
[docs] def margin(self): + return max((e.margin() + e.startwidth() for e in self.types)) - self.types[0].startwidth()
+ + def __call__(self, length, **kw): + if length and abs(length - self.length) > 1E-5: + raise ValueError("Wrong length for CompoundEdge") + lastwidth = self.types[0].startwidth() + + for e, l in zip(self.types, self.lengths): + self.step(e.startwidth() - lastwidth) + e(l) + lastwidth = e.endwidth()
+ + +############################################################################# +#### Slots +############################################################################# + +
[docs]class Slot(BaseEdge): + """Edge with an slot to slid another pice through """ + + description = "Slot" + + def __init__(self, boxes, depth): + super(Slot, self).__init__(boxes, None) + + self.depth = depth + + def __call__(self, length, **kw): + if self.depth: + self.boxes.corner(90) + self.boxes.edge(self.depth) + self.boxes.corner(-90) + self.boxes.edge(length) + self.boxes.corner(-90) + self.boxes.edge(self.depth) + self.boxes.corner(90) + else: + self.boxes.edge(self.length)
+ + +
[docs]class SlottedEdge(BaseEdge): + """Edge with multiple slots""" + description = "Straight Edge with slots" + + def __init__(self, boxes, sections, edge="e", slots=0): + super(SlottedEdge, self).__init__(boxes, Settings(boxes.thickness)) + + self.edge = self.edges.get(edge, edge) + self.sections = sections + self.slots = slots + +
[docs] def startwidth(self): + return self.edge.startwidth()
+ +
[docs] def endwidth(self): + return self.edge.endwidth()
+ +
[docs] def margin(self): + return self.edge.margin()
+ + def __call__(self, length, **kw): + + for l in self.sections[:-1]: + self.edge(l) + + if self.slots: + Slot(self.boxes, self.slots)(self.settings.thickness) + else: + self.boxes.edge(self.settings.thickness) + + self.edge(self.sections[-1])
+ + +############################################################################# +#### Finger Joints +############################################################################# + +
[docs]class FingerJointSettings(Settings): + """Settings for Finger Joints + +Values: + +* absolute + * style : "rectangular" : style of the fingers + * surroundingspaces : 2.0 : space at the start and end in multiple of normal spaces + * angle: 90 : Angle of the walls meeting + +* relative (in multiples of thickness) + + * space : 2.0 : space between fingers (multiples of thickness) + * finger : 2.0 : width of the fingers (multiples of thickness) + * width : 1.0 : width of finger holes (multiples of thickness) + * edge_width : 1.0 : space below holes of FingerHoleEdge (multiples of thickness) + * play : 0.0 : extra space to allow finger move in and out (multiples of thickness) + * extra_length : 0.0 : extra material to grind away burn marks (multiples of thickness) +""" + + absolute_params = { + "style" : ("rectangular", "springs", "barbs", "snap"), + "surroundingspaces": 2.0, + "angle" : 90.0, + } + + relative_params = { + "space": 2.0, + "finger": 2.0, + "width": 1.0, + "edge_width": 1.0, + "play" : 0.0, + "extra_length" : 0.0, + } + +
[docs] def checkValues(self): + if abs(self.space + self.finger) < 0.1: + raise ValueError("FingerJointSettings: space + finger must not be close to zero")
+ +
[docs] def edgeObjects(self, boxes, chars="fFh", add=True): + edges = [FingerJointEdge(boxes, self), + FingerJointEdgeCounterPart(boxes, self), + FingerHoleEdge(boxes, self), + ] + return self._edgeObjects(edges, boxes, chars, add)
+ +
[docs]class FingerJointBase: + +
[docs] def calcFingers(self, length, bedBolts): + space, finger = self.settings.space, self.settings.finger + fingers = int((length - (self.settings.surroundingspaces - 1) * space) // + (space + finger)) + # shrink surrounding space up to half a thickness each side + if fingers == 0 and length > finger + 1.0 * self.settings.thickness: + fingers = 1 + if not finger: + fingers = 0 + if bedBolts: + fingers = bedBolts.numFingers(fingers) + leftover = length - fingers * (space + finger) + space + + if fingers <= 0: + fingers = 0 + leftover = length + + return fingers, leftover
+ +
[docs] def fingerLength(self, angle): + # sharp corners + if angle >=90 or angle <= -90: + return self.settings.thickness + self.settings.extra_length, 0 + + # inner blunt corners + if angle < 0: + return (math.sin(math.radians(-angle)) * self.settings.thickness + + self.settings.extra_length), 0 + + # 0 to 90 (blunt corners) + a = 90 - (180-angle) / 2.0 + fingerlength = self.settings.thickness * math.tan(math.radians(a)) + b = 90-2*a + spacerecess = -math.sin(math.radians(b)) * fingerlength + return fingerlength + self.settings.extra_length, spacerecess
+ +
[docs]class FingerJointEdge(BaseEdge, FingerJointBase): + """Finger joint edge """ + char = 'f' + description = "Finger Joint" + positive = True + +
[docs] def draw_finger(self, f, h, style, positive=True, firsthalf=True): + t = self.settings.thickness + + if positive: + if style == "springs": + self.polyline( + 0, -90, 0.8*h, (90, 0.2*h), + 0.1 * h, 90, 0.9*h, -180, 0.9*h, 90, + f - 0.6*h, + 90, 0.9*h, -180, 0.9*h, 90, 0.1*h, + (90, 0.2 *h), 0.8*h, -90) + elif style == "barbs": + n = int((h-0.1*t) // (0.3*t)) + a = math.degrees(math.atan(0.5)) + l = 5**0.5 + poly = [h - n*0.3*t] + \ + ([-45, 0.1*2**0.5*t, 45+a, l*0.1*t, -a, 0] * n) + self.polyline( + 0, -90, *poly, 90, f, 90, *reversed(poly), -90 + ) + elif style == "snap" and f > 1.9 * t: + a12 = math.degrees(math.atan(0.5)) + l12 = t / math.cos(math.radians(a12)) + d = 4*t + d2 = d + 1*t + a = math.degrees(math.atan((0.5*t)/(h+d2))) + l = (h+d2) / math.cos(math.radians(a)) + poly = [0, 90, d, -180, d+h, -90, 0.5*t, 90+a12, l12, 90-a12, + 0.5*t, 90-a, l, +a, 0, (-180, 0.1*t), h+d2, 90, f-1.7*t, 90-a12, l12, a12, h, -90, 0] + if firsthalf: + poly = list(reversed(poly)) + self.polyline(*poly) + else: + self.polyline(0, -90, h, 90, f, 90, h, -90) + else: + self.polyline(0, 90, h, -90, f, -90, h, 90)
+ + def __call__(self, length, bedBolts=None, bedBoltSettings=None, **kw): + + positive = self.positive + t = self.settings.thickness + + s, f = self.settings.space, self.settings.finger + thickness = self.settings.thickness + style = self.settings.style + play = self.settings.play + + fingers, leftover = self.calcFingers(length, bedBolts) + + # not enough space for normal fingers - use small rectangular one + if (fingers == 0 and f and + leftover > 0.75*thickness and leftover > 4*play): + fingers = 1 + f = leftover = leftover / 2.0 + bedBolts = None + style = "rectangular" + + if not positive: + f += play + s -= play + leftover -= play + + self.edge(leftover / 2.0, tabs=1) + + l1,l2 = self.fingerLength(self.settings.angle) + h = l1-l2 + + d = (bedBoltSettings or self.bedBoltSettings)[0] + + for i in range(fingers): + if i != 0: + if not positive and bedBolts and bedBolts.drawBolt(i): + self.hole(0.5 * s, + 0.5 * self.settings.thickness, 0.5 * d) + + if positive and bedBolts and bedBolts.drawBolt(i): + self.bedBoltHole(s, bedBoltSettings) + else: + self.edge(s) + self.draw_finger(f, h, style, + positive, i < fingers//2) + + self.edge(leftover / 2.0, tabs=1) + +
[docs] def margin(self): + """ """ + widths = self.fingerLength(self.settings.angle) + if self.positive: + if self.settings.style == "snap": + return widths[0] - widths[1] + self.settings.thickness + return widths[0] - widths[1] + else: + return 0
+ +
[docs] def startwidth(self): + widths = self.fingerLength(self.settings.angle) + return widths[self.positive]
+ + +
[docs]class FingerJointEdgeCounterPart(FingerJointEdge): + """Finger joint edge - other side""" + char = 'F' + description = "Finger Joint (opposing side)" + positive = False
+ + +
[docs]class FingerHoles(FingerJointBase): + """Hole matching a finger joint edge""" + + def __init__(self, boxes, settings): + self.boxes = boxes + self.ctx = boxes.ctx + self.settings = settings + + def __call__(self, x, y, length, angle=90, bedBolts=None, bedBoltSettings=None): + """ + Draw holes for a matching finger joint edge + + :param x: position + :param y: position + :param length: length of matching edge + :param angle: (Default value = 90) + :param bedBolts: (Default value = None) + :param bedBoltSettings: (Default value = None) + + """ + with self.boxes.saved_context(): + self.boxes.moveTo(x, y, angle) + s, f = self.settings.space, self.settings.finger + p = self.settings.play + b = self.boxes.burn + fingers, leftover = self.calcFingers(length, bedBolts) + + # not enough space for normal fingers - use small rectangular one + if (fingers == 0 and f and + leftover > 0.75*self.settings.thickness and leftover > 4*p): + fingers = 1 + f = leftover = leftover / 2.0 + bedBolts = None + + if self.boxes.debug: + self.ctx.rectangle(b, -self.settings.width / 2 + b, + length - 2 * b, self.settings.width - 2 * b) + for i in range(fingers): + pos = leftover / 2.0 + i * (s + f) + + if bedBolts and bedBolts.drawBolt(i): + d = (bedBoltSettings or self.boxes.bedBoltSettings)[0] + self.boxes.hole(pos - 0.5 * s, 0, d * 0.5) + + self.boxes.rectangularHole(pos + 0.5 * f, 0, + f+p, self.settings.width+p)
+ +
[docs]class FingerHoleEdge(BaseEdge): + """Edge with holes for a parallel finger joint""" + char = 'h' + description = "Edge (parallel Finger Joint Holes)" + + def __init__(self, boxes, fingerHoles=None, **kw): + settings = None + if isinstance(fingerHoles, Settings): + settings = fingerHoles + fingerHoles = FingerHoles(boxes, settings) + super(FingerHoleEdge, self).__init__(boxes, settings, **kw) + + self.fingerHoles = fingerHoles or boxes.fingerHolesAt + + def __call__(self, length, bedBolts=None, bedBoltSettings=None, **kw): + dist = self.fingerHoles.settings.edge_width + with self.saved_context(): + self.fingerHoles( + 0, self.burn + dist + self.settings.thickness / 2, length, 0, + bedBolts=bedBolts, bedBoltSettings=bedBoltSettings) + self.edge(length, tabs=2) + +
[docs] def startwidth(self): + """ """ + return self.fingerHoles.settings.edge_width + self.settings.thickness
+ + +
[docs]class CrossingFingerHoleEdge(Edge): + """Edge with holes for finger joints 90° above""" + + description = "Edge (orthogonal Finger Joint Holes)" + char = '|' + + def __init__(self, boxes, height, fingerHoles=None, **kw): + super(CrossingFingerHoleEdge, self).__init__(boxes, None, **kw) + + self.fingerHoles = fingerHoles or boxes.fingerHolesAt + self.height = height + + def __call__(self, length, **kw): + self.fingerHoles(length / 2.0, self.burn, self.height) + super(CrossingFingerHoleEdge, self).__call__(length)
+ + +############################################################################# +#### Stackable Joints +############################################################################# + +
[docs]class StackableSettings(Settings): + """Settings for Stackable Edges + +Values: + +* absolute_params + + * angle : 60 : inside angle of the feet + +* relative (in multiples of thickness) + + * height : 2.0 : height of the feet (multiples of thickness) + * width : 4.0 : width of the feet (multiples of thickness) + * holedistance : 1.0 : distance from finger holes to bottom edge (multiples of thickness) + +""" + + absolute_params = { + "angle": 60, + } + + relative_params = { + "height": 2.0, + "width": 4.0, + "holedistance": 1.0, + } + +
[docs] def checkValues(self): + if self.angle < 20: + raise ValueError("StackableSettings: 'angle' is too small. Use value >= 20") + if self.angle > 260: + raise ValueError("StackableSettings: 'angle' is too big. Use value < 260")
+ +
[docs] def edgeObjects(self, boxes, chars="sSšŠ", add=True, fingersettings=None): + fingersettings = fingersettings or boxes.edges["f"].settings + edges = [StackableEdge(boxes, self, fingersettings), + StackableEdgeTop(boxes, self, fingersettings), + StackableFeet(boxes, self, fingersettings), + StackableHoleEdgeTop(boxes, self, fingersettings), + ] + return self._edgeObjects(edges, boxes, chars, add)
+ +
[docs]class StackableBaseEdge(BaseEdge): + """Edge for having stackable Boxes. The Edge creates feet on the bottom + and has matching recesses on the top corners.""" + + char = "s" + description = "Abstract Stackable class" + bottom = True + + def __init__(self, boxes, settings, fingerjointsettings): + super().__init__(boxes, settings) + + self.fingerjointsettings = fingerjointsettings + + def __call__(self, length, **kw): + s = self.settings + r = s.height / 2.0 / (1 - math.cos(math.radians(s.angle))) + l = r * math.sin(math.radians(s.angle)) + p = 1 if self.bottom else -1 + + self.boxes.edge(s.width, tabs=1) + self.boxes.corner(p * s.angle, r) + self.boxes.corner(-p * s.angle, r) + self.boxes.edge(length - 2 * s.width - 4 * l) + self.boxes.corner(-p * s.angle, r) + self.boxes.corner(p * s.angle, r) + self.boxes.edge(s.width, tabs=1) + + def _height(self): + return self.settings.height + self.settings.holedistance + self.settings.thickness + +
[docs] def startwidth(self): + return self._height() if self.bottom else 0
+ +
[docs] def margin(self): + return 0 if self.bottom else self.settings.height
+ +
[docs]class StackableEdge(StackableBaseEdge): + """Edge for having stackable Boxes. The Edge creates feet on the bottom + and has matching recesses on the top corners.""" + + char = "s" + description = "Stackable (bottom, finger joint holes)" + + def __call__(self, length, **kw): + s = self.settings + self.boxes.fingerHolesAt( + 0, + s.height + s.holedistance + 0.5 * self.boxes.thickness, + length, 0) + super().__call__(length, **kw)
+ +
[docs]class StackableEdgeTop(StackableBaseEdge): + char = "S" + description = "Stackable (top)" + bottom = False
+ +
[docs]class StackableFeet(StackableBaseEdge): + char = "š" + description = "Stackable feet (bottom)" + + def _height(self): + return self.settings.height
+ +
[docs]class StackableHoleEdgeTop(StackableBaseEdge): + char = "Š" + description = "Stackable edge with finger holes (top)" + bottom = False + +
[docs] def startwidth(self): + return self.settings.thickness + self.settings.holedistance
+ + def __call__(self, length, **kw): + s = self.settings + self.boxes.fingerHolesAt( + 0, + s.holedistance + 0.5 * self.boxes.thickness, + length, 0) + super().__call__(length, **kw)
+ +############################################################################# +#### Hinges +############################################################################# + +
[docs]class HingeSettings(Settings): + """Settings for Hinges and HingePins +Values: + +* absolute_params + + * style : "outset" : "outset" or "flush" + * outset : False : have lid overlap at the sides (similar to OutSetEdge) + * pinwidth : 1.0 : set to lower value to get disks surrounding the pins + * grip_percentage" : 0 : percentage of the lid that should get grips + +* relative (in multiples of thickness) + + * hingestrength : 1 : thickness of the arc holding the pin in place (multiples of thickness) + * axle : 2 : diameter of the pin hole (multiples of thickness) + * grip_length : 0 : fixed length of the grips on he lids (multiples of thickness) + +""" + absolute_params = { + "style": ("outset", "flush"), + "outset": False, + "pinwidth": 0.5, + "grip_percentage": 0, + } + + relative_params = { + "hingestrength": 1, # 1.5-0.5*2**0.5, + "axle": 2.0, + "grip_length": 0, + } + +
[docs] def checkValues(self): + if self.axle / self.thickness < 0.1: + raise ValueError("HingeSettings: 'axle' need to be at least 0.1 strong")
+ +
[docs] def edgeObjects(self, boxes, chars="iIjJkK", add=True): + edges = [ + Hinge(boxes, self, 1), + HingePin(boxes, self, 1), + Hinge(boxes, self, 2), + HingePin(boxes, self, 2), + Hinge(boxes, self, 3), + HingePin(boxes, self, 3), + ] + return self._edgeObjects(edges, boxes, chars, add)
+ +
[docs]class Hinge(BaseEdge): + char = 'i' + description = "Straight edge with hinge eye" + + def __init__(self, boxes, settings=None, layout=1): + super(Hinge, self).__init__(boxes, settings) + + if not (0 < layout <= 3): + raise ValueError("layout must be 1, 2 or 3 (got %i)" % layout) + + self.layout = layout + self.char = "eijk"[layout] + self.description = self.description + ('', ' (start)', ' (end)', ' (both ends)')[layout] + +
[docs] def margin(self): + return 3 * self.settings.thickness
+ +
[docs] def outset(self, _reversed=False): + t = self.settings.thickness + r = 0.5 * self.settings.axle + alpha = math.degrees(math.asin(0.5 * t / r)) + pinl = (self.settings.axle ** 2 - self.settings.thickness ** 2) ** 0.5 * self.settings.pinwidth + pos = math.cos(math.radians(alpha)) * r + hinge = ( + 0, + 90 - alpha, 0, + (-360, r), 0, + 90 + alpha, + t, + 90, + 0.5 * t, + (180, t + pos), 0, + (-90, 0.5 * t), 0 + ) + + if _reversed: + hinge = reversed(hinge) + self.polyline(*hinge) + self.boxes.rectangularHole(-pos, -0.5 * t, pinl, self.settings.thickness) + else: + self.boxes.rectangularHole(pos, -0.5 * t, pinl, self.settings.thickness) + self.polyline(*hinge)
+ +
[docs] def outsetlen(self): + t = self.settings.thickness + r = 0.5 * self.settings.axle + alpha = math.degrees(math.asin(0.5 * t / r)) + pos = math.cos(math.radians(alpha)) * r + + return 2 * pos + 1.5 * t
+ +
[docs] def flush(self, _reversed=False): + t = self.settings.thickness + + hinge = ( + 0, -90, + 0.5 * t, + (180, 0.5 * self.settings.axle + self.settings.hingestrength), 0, + (-90, 0.5 * t), 0 + ) + pos = 0.5 * self.settings.axle + self.settings.hingestrength + pinl = (self.settings.axle ** 2 - self.settings.thickness ** 2) ** 0.5 * self.settings.pinwidth + + if _reversed: + hinge = reversed(hinge) + self.hole(0.5 * t + pos, -0.5 * t, 0.5 * self.settings.axle) + self.boxes.rectangularHole(0.5 * t + pos, -0.5 * t, pinl, self.settings.thickness) + else: + self.hole(pos, -0.5 * t, 0.5 * self.settings.axle) + self.boxes.rectangularHole(pos, -0.5 * t, pinl, self.settings.thickness) + + self.polyline(*hinge)
+ +
[docs] def flushlen(self): + return self.settings.axle + 2 * self.settings.hingestrength + 0.5 * self.settings.thickness
+ + def __call__(self, l, **kw): + hlen = getattr(self, self.settings.style + 'len', self.outsetlen)() + + if self.layout & 1: + getattr(self, self.settings.style, self.outset)() + + self.edge(l - (self.layout & 1) * hlen - bool(self.layout & 2) * hlen, + tabs=2) + + if self.layout & 2: + getattr(self, self.settings.style, self.outset)(True)
+ + +
[docs]class HingePin(BaseEdge): + char = 'I' + description = "Edge with hinge pin" + + def __init__(self, boxes, settings=None, layout=1): + super(HingePin, self).__init__(boxes, settings) + + if not (0 < layout <= 3): + raise ValueError("layout must be 1, 2 or 3 (got %i)" % layout) + + self.layout = layout + self.char = "EIJK"[layout] + self.description = self.description + ('', ' (start)', ' (end)', ' (both ends)')[layout] + +
[docs] def startwidth(self): + if self.layout & 1: + return 0 + else: + return self.settings.outset * self.boxes.thickness
+ +
[docs] def endwidth(self): + if self.layout & 2: + return 0 + else: + return self.settings.outset * self.boxes.thickness
+ +
[docs] def margin(self): + return self.settings.thickness
+ +
[docs] def outset(self, _reversed=False): + t = self.settings.thickness + r = 0.5 * self.settings.axle + alpha = math.degrees(math.asin(0.5 * t / r)) + pos = math.cos(math.radians(alpha)) * r + pinl = (self.settings.axle ** 2 - self.settings.thickness ** 2) ** 0.5 * self.settings.pinwidth + pin = (pos - 0.5 * pinl, -90, + t, 90, + pinl, + 90, + t, + -90) + + if self.settings.outset: + pin += ( + pos - 0.5 * pinl + 1.5 * t, + -90, + t, + 90, + 0, + ) + else: + pin += (pos - 0.5 * pinl,) + + if _reversed: + pin = reversed(pin) + + self.polyline(*pin)
+ +
[docs] def outsetlen(self): + t = self.settings.thickness + r = 0.5 * self.settings.axle + alpha = math.degrees(math.asin(0.5 * t / r)) + pos = math.cos(math.radians(alpha)) * r + + if self.settings.outset: + return 2 * pos + 1.5 * self.settings.thickness + else: + return 2 * pos
+ +
[docs] def flush(self, _reversed=False): + t = self.settings.thickness + pinl = (self.settings.axle ** 2 - t ** 2) ** 0.5 * self.settings.pinwidth + d = (self.settings.axle - pinl) / 2.0 + pin = (self.settings.hingestrength + d, -90, + t, 90, + pinl, + 90, + t, + -90, d) + + if self.settings.outset: + pin += ( + 0, + self.settings.hingestrength + 0.5 * t, + -90, + t, + 90, + 0, + ) + + if _reversed: + pin = reversed(pin) + + self.polyline(*pin)
+ +
[docs] def flushlen(self): + l = self.settings.hingestrength + self.settings.axle + + if self.settings.outset: + l += self.settings.hingestrength + 0.5 * self.settings.thickness + + return l
+ + def __call__(self, l, **kw): + plen = getattr(self, self.settings.style + 'len', self.outsetlen)() + glen = l * self.settings.grip_percentage + \ + self.settings.grip_length + + if not self.settings.outset: + glen = 0.0 + + glen = min(glen, l - plen) + + if self.layout & 1 and self.layout & 2: + getattr(self, self.settings.style, self.outset)() + self.edge(l - 2 * plen, tabs=2) + getattr(self, self.settings.style, self.outset)(True) + elif self.layout & 1: + getattr(self, self.settings.style, self.outset)() + self.edge(l - plen - glen, tabs=2) + self.edges['g'](glen) + else: + self.edges['g'](glen) + self.edge(l - plen - glen, tabs=2) + getattr(self, self.settings.style, self.outset)(True)
+ +############################################################################# +#### Chest Hinge +############################################################################# + +
[docs]class ChestHingeSettings(Settings): + """Settings for Chest Hinges +Values: + +* relative (in multiples of thickness) + + * pin_height : 2.0 : radius of the disc rotating in the hinge (multiples of thickness) + * hinge_strength : 1.0 : thickness of the arc holding the pin in place (multiples of thickness) + +* absolute + + * finger_joints_on_box : False : whether to include finger joints on the edge with the box + * finger_joints_on_lid : False : whether to include finger joints on the edge with the lid +""" + + relative_params = { + "pin_height" : 2.0, + "hinge_strength" : 1.0, + "play" : 0.1, + } + + absolute_params = { + "finger_joints_on_box" : False, + "finger_joints_on_lid" : False, + } + +
[docs] def checkValues(self): + if self.pin_height / self.thickness < 1.2: + raise ValueError("ChestHingeSettings: 'pin_height' must be >= 1.2")
+ +
[docs] def pinheight(self): + return ((0.9*self.pin_height)**2-self.thickness**2)**0.5
+ +
[docs] def edgeObjects(self, boxes, chars="oOpPqQ", add=True): + edges = [ + ChestHinge(boxes, self), + ChestHinge(boxes, self, 1), + ChestHingeTop(boxes, self), + ChestHingeTop(boxes, self, 1), + ChestHingePin(boxes, self), + ChestHingeFront(boxes, self), + ] + return self._edgeObjects(edges, boxes, chars, add)
+ +
[docs]class ChestHinge(BaseEdge): + + description = "Edge with chest hinge" + + char = "o" + + def __init__(self, boxes, settings=None, reversed=False): + super(ChestHinge, self).__init__(boxes, settings) + + self.reversed = reversed + self.char = "oO"[reversed] + self.description = self.description + (' (start)', ' (end)')[reversed] + + def __call__(self, l, **kw): + t = self.settings.thickness + p = self.settings.pin_height + s = self.settings.hinge_strength + pinh = self.settings.pinheight() + if self.reversed: + self.hole(l+t, 0, p, tabs=4) + self.rectangularHole(l+0.5*t, -0.5*pinh, t, pinh) + else: + self.hole(-t, -s-p, p, tabs=4) + self.rectangularHole(-0.5*t, -s-p-0.5*pinh, t, pinh) + + if self.settings.finger_joints_on_box: + final_segment = t-s + draw_rest_of_edge = lambda : self.edges["F"](l-p) + else: + final_segment = l+t-p-s + draw_rest_of_edge = lambda : None + + poly = (0, -180, t, (270, p+s), 0, -90, final_segment) + + if self.reversed: + draw_rest_of_edge() + self.polyline(*reversed(poly)) + else: + self.polyline(*poly) + draw_rest_of_edge() + +
[docs] def margin(self): + if self.reversed: + return 0*(self.settings.pin_height+self.settings.hinge_strength) + else: + return 1*(self.settings.pin_height+self.settings.hinge_strength)
+ +
[docs] def startwidth(self): + if self.reversed: + return self.settings.pin_height+self.settings.hinge_strength + return 0
+ +
[docs] def endwidth(self): + if self.reversed: + return 0 + return self.settings.pin_height+self.settings.hinge_strength
+ +
[docs]class ChestHingeTop(ChestHinge): + + "Edge above a chest hinge" + + char = "p" + + def __init__(self, boxes, settings=None, reversed=False): + super(ChestHingeTop, self).__init__(boxes, settings) + + self.reversed = reversed + self.char = "oO"[reversed] + self.description = self.description + (' (start)', ' (end)')[reversed] + + def __call__(self, l, **kw): + t = self.settings.thickness + p = self.settings.pin_height + s = self.settings.hinge_strength + play = self.settings.play + + if self.settings.finger_joints_on_lid: + final_segment = t-s-play + draw_rest_of_edge = lambda : self.edges["F"](l-p) + else: + final_segment = l+t-p-s-play + draw_rest_of_edge = lambda : None + + poly = (0, -180, t, -180, 0, (-90, p+s+play), 0, 90, final_segment) + + if self.reversed: + draw_rest_of_edge() + self.polyline(*reversed(poly)) + else: + self.polyline(*poly) + draw_rest_of_edge() + +
[docs] def startwidth(self): + if self.reversed: + return self.settings.play+self.settings.pin_height+self.settings.hinge_strength + return 0
+ +
[docs] def endwidth(self): + if self.reversed: + return 0 + return self.settings.play+self.settings.pin_height+self.settings.hinge_strength
+ +
[docs] def margin(self): + if self.reversed: + return 0. + else: + return 1*(self.settings.play+self.settings.pin_height+self.settings.hinge_strength)
+ +
[docs]class ChestHingePin(BaseEdge): + + description = "Edge with pins for an chest hinge" + + char = "q" + + def __call__(self, l, **kw): + t = self.settings.thickness + p = self.settings.pin_height + s = self.settings.hinge_strength + pinh = self.settings.pinheight() + + if self.settings.finger_joints_on_lid: + middle_segment = [0] + draw_rest_of_edge = lambda : self.edges["F"](l+2*t) + else: + middle_segment = [l+2*t,] + draw_rest_of_edge = lambda : None + + poly = [0, -90, s+p-pinh, -90, t, 90, pinh, 90,] + self.polyline(*poly) + draw_rest_of_edge() + self.polyline(*(middle_segment + list(reversed(poly)))) + +
[docs] def margin(self): + return (self.settings.pin_height+self.settings.hinge_strength)
+ + +
[docs]class ChestHingeFront(Edge): + + description = "Edge opposing a chest hinge" + + char = "Q" + +
[docs] def startwidth(self): + return self.settings.pin_height+self.settings.hinge_strength
+ +############################################################################# +#### Cabinet Hinge +############################################################################# + +
[docs]class CabinetHingeSettings(Settings): + """Settings for Cabinet Hinges +Values: + +* absolute_params + + * bore : 3.2 : diameter of the pin hole in mm + * eyes_per_hinge : 5 : pieces per hinge + * hinges : 2 : number of hinges per edge + * style : inside : style of hinge used + +* relative (in multiples of thickness) + + * eye : 1.5 : radius of the eye (multiples of thickness) + * play : 0.05 : space between eyes (multiples of thickness) + * spacing : 2.0 : minimum space around the hinge (multiples of thickness) +""" + absolute_params = { + "bore": 3.2, + "eyes_per_hinge" : 5, + "hinges" : 2, + "style" : ("inside", "outside"), + } + + relative_params = { + "eye": 1.5, + "play" : 0.05, + "spacing": 2.0, + } + +
[docs] def edgeObjects(self, boxes, chars="uUvV", add=True): + edges = [CabinetHingeEdge(boxes, self), + CabinetHingeEdge(boxes, self, top=True), + CabinetHingeEdge(boxes, self, angled=True), + CabinetHingeEdge(boxes, self, top=True, angled=True), + ] + for e, c in zip(edges, chars): + e.char = c + return self._edgeObjects(edges, boxes, chars, add)
+ +
[docs]class CabinetHingeEdge(BaseEdge): + """Edge with cabinet hinges""" + + char = "u" + description = "Edge with cabinet hinges" + + def __init__(self, boxes, settings=None, top=False, angled=False): + super(CabinetHingeEdge, self).__init__(boxes, settings) + self.top = top + self.angled = angled + self.char = "uUvV"[bool(top)+2*bool(angled)] + +
[docs] def startwidth(self): + return self.settings.thickness if self.top and self.angled else 0.0
+ + + def __poly(self): + n = self.settings.eyes_per_hinge + p = self.settings.play + e = self.settings.eye + t = self.settings.thickness + spacing = self.settings.spacing + + if self.settings.style == "outside" and self.angled: + e = t + elif self.angled and not self.top: + # move hinge up to leave space for lid + e -= t + + if self.top: + # start with space + poly = [spacing, 90, e+p] + else: + # start with hinge eye + poly = [spacing+p, 90, e+p, 0] + for i in range(n): + if (i % 2) ^ self.top: + # space + if i == 0: + poly += [-90, t + 2*p, 90] + else: + poly += [90, t + 2*p, 90] + else: + # hinge eye + poly += [t-p, -90, t, -90, t-p] + + if (n % 2) ^ self.top: + # stopped with hinge eye + poly += [0, e+p, 90, p+spacing] + else: + # stopped with space + poly[-1:] = [-90, e+p, 90, 0+spacing ] + + width = (t+p) * n + p + 2 * spacing + + return poly, width + + def __call__(self, l, **kw): + n = self.settings.eyes_per_hinge + p = self.settings.play + e = self.settings.eye + t = self.settings.thickness + hn = self.settings.hinges + + poly, width = self.__poly() + + if self.settings.style == "outside" and self.angled: + e = t + elif self.angled and not self.top: + # move hinge up to leave space for lid + e -= t + + hn = min(hn, int(l // width)) + + if hn == 1: + self.edge((l-width) / 2, tabs=2) + + for j in range(hn): + for i in range(n): + if not (i % 2) ^ self.top: + self.rectangularHole(self.settings.spacing+0.5*t+p+i*(t+p), e+2.5*t, t, t) + self.polyline(*poly) + if j < (hn - 1): + self.edge((l-hn*width) / (hn-1), tabs=2) + + if hn == 1: + self.edge((l-width) / 2, tabs=2) + +
[docs] def parts(self, move=None): + e, b = self.settings.eye, self.settings.bore + t = self.settings.thickness + + n = self.settings.eyes_per_hinge * self.settings.hinges + pairs = n // 2 + 2 * (n % 2) + + if self.settings.style == "outside": + th = 2*e + 4*t + tw = n * (max(3*t, 2*e) + self.boxes.spacing) + else: + th = 4*e+3*t+self.boxes.spacing + tw = max(e, 2*t) * pairs + + if self.move(tw, th, move, True, label="hinges"): + return + + if self.settings.style == "outside": + ax = max(t/2, e-t) + self.moveTo(t+ax) + for i in range(n): + if self.angled: + if i > n // 2: + l = 4 * t + ax + else: + l = 5 * t + ax + else: + l = 3 * t + e + self.hole(0, e, b/2.0) + da = math.asin((t-ax) / e) + dad = math.degrees(da) + dy = e * (1-math.cos(da)) + self.polyline(0, (180-dad, e), 0, (-90+dad), dy+l-e, (90, t)) + self.polyline(0, 90, t, -90, t, 90, t, 90, t, -90, t, -90, t, + 90, t, 90, (ax+t)-e, -90, l-3*t, (90, e)) + self.moveTo(2*max(e, 1.5*t) + self.boxes.spacing) + + self.move(tw, th, move, label="hinges") + return + + if e <= 2*t: + if self.angled: + corner = [2*e-t, (90, 2*t - e), 0, -90, t, (90, e)] + else: + corner = [2*e, (90, 2*t)] + else: + a = math.asin(2*t/e) + ang = math.degrees(a) + corner = [e*(1-math.cos(a))+2*t, -90+ang, 0, (180-ang, e)] + self.moveTo(max(e, 2*t)) + for i in range(n): + self.hole(0, e, b/2.0) + self.polyline(*[0, (180, e), 0, -90, t, 90, t, -90, t, -90, t, 90, t, 90, t, (90, t)] + corner) + self.moveTo(self.boxes.spacing, 4*e+3*t+self.boxes.spacing, 180) + if i % 2: + self.moveTo(2*max(e, 2*t) + 2*self.boxes.spacing) + + self.move(th, tw, move, label="hinges")
+ +############################################################################# +#### Slide-on lid +############################################################################# + +
[docs]class LidSettings(FingerJointSettings): + + """Settings for Slide-on Lids + +Note that edge_width below also determines how much the sides extend above the lid. + +Values: + +* absolute_params + + * second_pin : True : additional pin for better positioning + * spring : "both" : position(s) of the extra locking springs in the lid + * hole_width : 0 : width of the "finger hole" in mm + + + """ + __doc__ += FingerJointSettings.__doc__ + + absolute_params = FingerJointSettings.absolute_params.copy() + relative_params = FingerJointSettings.relative_params.copy() + + relative_params.update( { + "play": 0.05, + "finger": 3.0, + "space": 2.0, + } ) + + absolute_params.update( { + "second_pin": True, + "spring": ("both", "none", "left", "right"), + "hole_width": 0 + } ) + +
[docs] def edgeObjects(self, boxes, chars=None, add=True): + edges = [LidEdge(boxes, self), + LidHoleEdge(boxes, self), + LidRight(boxes, self), + LidLeft(boxes, self), + LidSideRight(boxes, self), + LidSideLeft(boxes, self), + ] + return self._edgeObjects(edges, boxes, chars, add)
+ +
[docs]class LidEdge(FingerJointEdge): + char = "l" + description = "Edge for slide on lid (back)" + + def __call__(self, length, bedBolts=None, bedBoltSettings=None, **kw): + hole_width = self.settings.hole_width; + if hole_width > 0: + super().__call__((length - hole_width) / 2) + GroovedEdgeBase.groove_arc(self, hole_width) + super().__call__((length - hole_width) / 2) + else: + super().__call__(length)
+ +
[docs]class LidHoleEdge(FingerHoleEdge): + char = "L" + description = "Edge for slide on lid (box back)" + + def __call__(self, length, bedBolts=None, bedBoltSettings=None, **kw): + hole_width = self.settings.hole_width; + if hole_width > 0: + super().__call__((length - hole_width) / 2) + self.edge(hole_width) + super().__call__((length - hole_width) / 2) + else: + super().__call__(length)
+ +
[docs]class LidRight(BaseEdge): + char = "n" + description = "Edge for slide on lid (right)" + rightside = True + + def __call__(self, length, **kw): + t = self.boxes.thickness + + if self.rightside: + spring = self.settings.spring in ("right", "both") + else: + spring = self.settings.spring in ("left", "both") + + if spring: + l = min(6*t, length - 2*t) + a = 30 + sqt = 0.4 * t / math.cos(math.radians(a)) + sw = 0.5 * t + p = [0, 90, 1.5*t+sw, -90, l, (-180, 0.25*t), l-0.2*t, 90, sw, 90-a, sqt, 2*a, sqt, -a, length-t ] + else: + p = [t, 90, t, -90, length-t] + + pin = self.settings.second_pin + + if pin: + pinl = 2*t + p[-1:] = [length-2*t-pinl, -90, t, 90, pinl, 90, t, -90, t] + + if not self.rightside: + p = list(reversed(p)) + self.polyline(*p) + +
[docs] def startwidth(self): + if self.rightside: # or self.settings.second_pin: + return self.boxes.thickness + else: + return 0.0
+ +
[docs] def endwidth(self): + if not self.rightside: # or self.settings.second_pin: + return self.boxes.thickness + else: + return 0.0
+ +
[docs] def margin(self): + if not self.rightside: # and not self.settings.second_pin: + return self.boxes.thickness + else: + return 0.0
+ +
[docs]class LidLeft(LidRight): + char = "m" + description = "Edge for slide on lid (left)" + rightside = False
+ +
[docs]class LidSideRight(BaseEdge): + char = "N" + description = "Edge for slide on lid (box right)" + + rightside = True + + def __call__(self, length, **kw): + t = self.boxes.thickness + s = self.settings.play + pin = self.settings.second_pin + edge_width = self.settings.edge_width + r = edge_width/3 + + if self.rightside: + spring = self.settings.spring in ("right", "both") + else: + spring = self.settings.spring in ("left", "both") + + if spring: + p = [s, -90, t+s, -90, t+s, 90, edge_width-s/2, 90, length+t] + else: + p = [t+s, -90, t+s, -90, 2*t+s, 90, edge_width-s/2, 90, length+t] + + if pin: + pinl = 2*t + p[-1:] = [p[-1]-1.5*t-2*pinl-r, (90, r), edge_width+t+s/2-r, -90, 2*pinl+s+0.5*t, -90, t+s, -90, + pinl-r, (90, r), edge_width-s/2-2*r, (90, r), pinl+t-s-r] + + holex = 0.6 * t + holey = -0.5*t + self.burn - s / 2 + if self.rightside: + p = list(reversed(p)) + holex = length - holex + holey = edge_width + 0.5*t + self.burn + + if spring: + self.rectangularHole(holex, holey, 0.4*t, t+2*s) + self.polyline(*p) + +
[docs] def startwidth(self): + return self.boxes.thickness + self.settings.edge_width if self.rightside else -self.settings.play / 2
+ +
[docs] def endwidth(self): + return self.boxes.thickness + self.settings.edge_width if not self.rightside else -self.settings.play / 2
+ +
[docs] def margin(self): + return self.boxes.thickness + self.settings.edge_width + self.settings.play / 2 if not self.rightside else 0.0
+ +
[docs]class LidSideLeft(LidSideRight): + char = "M" + description = "Edge for slide on lid (box left)" + rightside = False
+ +############################################################################# +#### Click Joints +############################################################################# + +
[docs]class ClickSettings(Settings): + """Settings for Click-on Lids +Values: + +* absolute_params + + * angle : 5.0 : angle of the hooks bending outward + +* relative (in multiples of thickness) + + * depth : 3.0 : length of the hooks (multiples of thickness) + * bottom_radius : 0.1 : radius at the bottom (multiples of thickness) +""" + + absolute_params = { + "angle": 5.0, + } + + relative_params = { + "depth": 3.0, + "bottom_radius": 0.1, + } + +
[docs] def edgeObjects(self, boxes, chars="cC", add=True): + edges = [ClickConnector(boxes, self), + ClickEdge(boxes, self)] + return self._edgeObjects(edges, boxes, chars, add)
+ +
[docs]class ClickConnector(BaseEdge): + char = "c" + description = "Click on (bottom side)" + +
[docs] def hook(self, reverse=False): + t = self.settings.thickness + a = self.settings.angle + d = self.settings.depth + r = self.settings.bottom_radius + c = math.cos(math.radians(a)) + s = math.sin(math.radians(a)) + + p1 = (0, 90 - a, c * d) + p2 = ( + d + t, + -90, + t * 0.5, + 135, + t * 2 ** 0.5, + 135, + d + 2 * t + s * 0.5 * t) + p3 = (c * d - s * c * 0.2 * t, -a, 0) + + if not reverse: + self.polyline(*p1) + self.corner(-180, r) + self.polyline(*p2) + self.corner(-180 + 2 * a, r) + self.polyline(*p3) + else: + self.polyline(*reversed(p3)) + self.corner(-180 + 2 * a, r) + self.polyline(*reversed(p2)) + self.corner(-180, r) + self.polyline(*reversed(p1))
+ +
[docs] def hookWidth(self): + t = self.settings.thickness + a = self.settings.angle + d = self.settings.depth + r = self.settings.bottom_radius + c = math.cos(math.radians(a)) + s = math.sin(math.radians(a)) + + return 2 * s * d * c + 0.5 * c * t + c * 4 * r
+ +
[docs] def hookOffset(self): + a = self.settings.angle + d = self.settings.depth + r = self.settings.bottom_radius + c = math.cos(math.radians(a)) + s = math.sin(math.radians(a)) + + return s * d * c + 2 * r
+ +
[docs] def finger(self, length): + t = self.settings.thickness + self.polyline( + 2 * t, + 90, + length, + 90, + 2 * t, + )
+ + def __call__(self, length, **kw): + t = self.settings.thickness + self.edge(4 * t) + self.hook() + self.finger(2 * t) + self.hook(reverse=True) + + self.edge(length - 2 * (6 * t + 2 * self.hookWidth()), tabs=2) + + self.hook() + self.finger(2 * t) + self.hook(reverse=True) + self.edge(4 * t) + +
[docs] def margin(self): + return 2 * self.settings.thickness
+ + +
[docs]class ClickEdge(ClickConnector): + char = "C" + description = "Click on (top)" + +
[docs] def startwidth(self): + return self.boxes.thickness
+ +
[docs] def margin(self): + return 0.0
+ + def __call__(self, length, **kw): + t = self.settings.thickness + o = self.hookOffset() + w = self.hookWidth() + p1 = ( + 4 * t + o, + 90, + t, + -90, + 2 * (t + w - o), + -90, + t, + 90, + 0) + self.polyline(*p1) + self.edge(length - 2 * (6 * t + 2 * w) + 2 * o, tabs=2) + self.polyline(*reversed(p1))
+ + +############################################################################# +#### Dove Tail Joints +############################################################################# + +
[docs]class DoveTailSettings(Settings): + """Settings for Dove Tail Joints + +Values: + +* absolute + + * angle : 50 : how much should fingers widen (-80 to 80) + +* relative (in multiples of thickness) + + * size : 3 : from one middle of a dove tail to another (multiples of thickness) + * depth : 1.5 : how far the dove tails stick out of/into the edge (multiples of thickness) + * radius : 0.2 : radius used on all four corners (multiples of thickness) + +""" + absolute_params = { + "angle": 50, + } + + relative_params = { + "size": 3, + "depth": 1.5, + "radius": 0.2, + } + +
[docs] def edgeObjects(self, boxes, chars="dD", add=True): + edges = [DoveTailJoint(boxes, self), + DoveTailJointCounterPart(boxes, self)] + return self._edgeObjects(edges, boxes, chars, add)
+ +
[docs]class DoveTailJoint(BaseEdge): + """Edge with dove tail joints """ + + char = 'd' + description = "Dove Tail Joint" + positive = True + + def __call__(self, length, **kw): + s = self.settings + radius = max(s.radius, self.boxes.burn) # no smaller than burn + positive = self.positive + a = s.angle + 90 + alpha = 0.5 * math.pi - math.pi * s.angle / 180.0 + + l1 = radius / math.tan(alpha / 2.0) + diffx = 0.5 * s.depth / math.tan(alpha) + l2 = 0.5 * s.depth / math.sin(alpha) + + sections = int((length) // (s.size * 2)) + leftover = length - sections * s.size * 2 + + if sections == 0: + self.edge(length) + return + + p = 1 if positive else -1 + + self.edge((s.size + leftover) / 2.0 + diffx - l1, tabs=1) + + for i in range(sections): + self.corner(-1 * p * a, radius) + self.edge(2 * (l2 - l1)) + self.corner(p * a, radius) + self.edge(2 * (diffx - l1) + s.size) + self.corner(p * a, radius) + self.edge(2 * (l2 - l1)) + self.corner(-1 * p * a, radius) + + if i < sections - 1: # all but the last + self.edge(2 * (diffx - l1) + s.size) + + self.edge((s.size + leftover) / 2.0 + diffx - l1, tabs=1) + +
[docs] def margin(self): + """ """ + return self.settings.depth
+ + +
[docs]class DoveTailJointCounterPart(DoveTailJoint): + """Edge for other side of dove joints """ + char = 'D' + description = "Dove Tail Joint (opposing side)" + + positive = False + +
[docs] def margin(self): + return 0.0
+ + +
[docs]class FlexSettings(Settings): + """Settings for Flex + +Values: + +* absolute + + * stretch : 1.05 : Hint of how much the flex part should be shortend + +* relative (in multiples of thickness) + + * distance : 0.5 : width of the pattern perpendicular to the cuts (multiples of thickness) + * connection : 1.0 : width of the gaps in the cuts (multiples of thickness) + * width : 5.0 : width of the pattern in direction of the cuts (multiples of thickness) + +""" + relative_params = { + "distance": 0.5, + "connection": 1.0, + "width": 5.0, + } + + absolute_params = { + "stretch": 1.05, + } + +
[docs] def checkValues(self): + if self.distance < 0.01: + raise ValueError("Flex Settings: distance parameter must be > 0.01mm") + if self.width < 0.1: + raise ValueError("Flex Settings: width parameter must be > 0.1mm")
+ +
[docs]class FlexEdge(BaseEdge): + """Edge with flex cuts - use straight edge for the opposing side""" + char = 'X' + description = "Flex cut" + + def __call__(self, x, h, **kw): + dist = self.settings.distance + connection = self.settings.connection + width = self.settings.width + + burn = self.boxes.burn + h += 2 * burn + lines = int(x // dist) + leftover = x - lines * dist + sections = max(int((h - connection) // width), 1) + sheight = ((h - connection) / sections) - connection + + self.ctx.stroke() + for i in range(1, lines): + pos = i * dist + leftover / 2 + + if i % 2: + self.ctx.move_to(pos, 0) + self.ctx.line_to(pos, connection + sheight) + + for j in range((sections - 1) // 2): + self.ctx.move_to(pos, (2 * j + 1) * sheight + (2 * j + 2) * connection) + self.ctx.line_to(pos, (2 * j + 3) * (sheight + connection)) + + if not sections % 2: + self.ctx.move_to(pos, h - sheight - connection) + self.ctx.line_to(pos, h) + else: + if sections % 2: + self.ctx.move_to(pos, h) + self.ctx.line_to(pos, h - connection - sheight) + + for j in range((sections - 1) // 2): + self.ctx.move_to( + pos, h - ((2 * j + 1) * sheight + (2 * j + 2) * connection)) + self.ctx.line_to( + pos, h - (2 * j + 3) * (sheight + connection)) + + else: + for j in range(sections // 2): + self.ctx.move_to(pos, + h - connection - 2 * j * (sheight + connection)) + self.ctx.line_to(pos, h - 2 * (j + 1) * (sheight + connection)) + + self.ctx.stroke() + self.ctx.move_to(0, 0) + self.ctx.line_to(x, 0) + self.ctx.translate(*self.ctx.get_current_point())
+ +
[docs]class GearSettings(Settings): + + """Settings for rack (and pinion) edge +Values: +* absolute_params + + * dimension : 3.0 : modulus of the gear (in mm) + * angle : 20.0 : pressure angle + * profile_shift : 20.0 : Profile shift + * clearance : 0.0 : clearance +""" + + absolute_params = { + "dimension" : 3.0, + "angle" : 20.0, + "profile_shift" : 20.0, + "clearance" : 0.0, + } + + relative_params = {}
+ +
[docs]class RackEdge(BaseEdge): + + char = "R" + + description = "Rack (and pinion) Edge" + + def __init__(self, boxes, settings): + super(RackEdge, self).__init__(boxes, settings) + self.gear = gears.Gears(boxes) + + def __call__(self, length, **kw): + params = self.settings.values.copy() + params["draw_rack"] = True + params["rack_base_height"] = -1E-36 + params["rack_teeth_length"] = int(length // params["dimension"]) + params["rack_base_tab"] = (length - (params["rack_teeth_length"]) * params["dimension"]) / 2.0 + s_tmp = self.boxes.spacing + self.boxes.spacing = 0 + self.moveTo(length, 0, 180) + self.gear(move="", **params) + self.moveTo(0, 0, 180) + self.boxes.spacing = s_tmp + +
[docs] def margin(self): + return self.settings.dimension * 1.1
+ +
[docs]class RoundedTriangleEdgeSettings(Settings): + + """Settings for RoundedTriangleEdge +Values: + +* absolute_params + + * height : 150. : height above the wall + * radius : 30. : radius of top corner + * r_hole : 0. : radius of hole + +* relative (in multiples of thickness) + + * outset : 0 : extend the triangle along the length of the edge (multiples of thickness) + +""" + + absolute_params = { + "height" : 50., + "radius" : 30., + "r_hole" : 2., + } + + relative_params = { + "outset" : 0., + } + +
[docs] def edgeObjects(self, boxes, chars="t", add=True): + edges = [RoundedTriangleEdge(boxes, self), + RoundedTriangleFingerHolesEdge(boxes, self)] + return self._edgeObjects(edges, boxes, chars, add)
+ +
[docs]class RoundedTriangleEdge(Edge): + """Makes an 'edge' with a rounded triangular bumpout and + optional hole""" + description = "Triangle for handle" + char = "t" + def __call__(self, length, **kw): + length += 2 * self.settings.outset + r = self.settings.radius + if r > length / 2: + r = length / 2 + if length-2*r < self.settings.height: # avoid division by zero + angle = 90-math.degrees(math.atan( + (length-2*r)/(2*self.settings.height))) + l = self.settings.height / math.cos(math.radians(90-angle)) + else: + angle = math.degrees(math.atan( + 2*self.settings.height/(length-2*r))) + l = 0.5 * (length-2*r) / math.cos(math.radians(angle)) + if self.settings.outset: + self.polyline(0, -180, self.settings.outset, 90) + else: + self.corner(-90) + if self.settings.r_hole: + self.hole(self.settings.height, length/2., self.settings.r_hole) + self.corner(90-angle, r, tabs=1) + self.edge(l, tabs=1) + self.corner(2*angle, r, tabs=1) + self.edge(l, tabs=1) + self.corner(90-angle, r, tabs=1) + if self.settings.outset: + self.polyline(0, 90, self.settings.outset, -180) + else: + self.corner(-90) + +
[docs] def margin(self): + return self.settings.height + self.settings.radius
+ +
[docs]class RoundedTriangleFingerHolesEdge(RoundedTriangleEdge): + + char = "T" + +
[docs] def startwidth(self): + return self.settings.thickness
+ + def __call__(self, length, **kw): + self.fingerHolesAt(0, 0.5*self.settings.thickness, length, 0) + super().__call__(length, **kw)
+ + +
[docs]class HandleEdgeSettings(Settings): + + """Settings for HandleEdge +Values: + +* absolute_params + + * height : 20. : height above the wall in mm + * radius : 10. : radius of corners in mm + * hole_width : "40:40" : width of hole(s) in percentage of maximum hole width (width of edge - (n+1) * material thickness) + * hole_height : 75. : height of hole(s) in percentage of maximum hole height (handle height - 2 * material thickness) + * on_sides : True, : added to side panels if checked, to front and back otherwise (only used with top_edge parameter) + +* relative + + * outset : 1. : extend the handle along the length of the edge (multiples of thickness) + +""" + + absolute_params = { + "height" : 20., + "radius" : 10., + "hole_width" : "40:40", + "hole_height" : 75., + "on_sides": True, + } + + relative_params = { + "outset" : 1., + } + +
[docs] def edgeObjects(self, boxes, chars="yY", add=True): + edges = [HandleEdge(boxes, self), + HandleHoleEdge(boxes, self)] + return self._edgeObjects(edges, boxes, chars, add)
+ +# inspiration came from https://www.thingiverse.com/thing:327393 + +
[docs]class HandleEdge(Edge): + """Extends an 'edge' by adding a rounded bumpout with optional holes""" + description = "Handle for e.g. a drawer" + char = "y" + extra_height = 0.0 + + def __call__(self, length, **kw): + length += 2 * self.settings.outset + extra_height = self.extra_height * self.settings.thickness + + r = self.settings.radius + if r > length / 2: + r = length / 2 + if r > self.settings.height: + r = self.settings.height + + widths = argparseSections(self.settings.hole_width) + + if self.settings.outset: + self.polyline(0, -180, self.settings.outset, 90) + else: + self.corner(-90) + + if self.settings.hole_height and sum(widths) > 0: + if sum(widths) < 100: + slot_offset = ((1 - sum(widths) / 100) * (length - (len(widths) + 1) * self.thickness)) / (len(widths) * 2) + else: + slot_offset = 0 + + slot_height = (self.settings.height - 2 * self.thickness) * self.settings.hole_height / 100 + slot_x = self.thickness + slot_offset + + for w in widths: + if sum(widths) > 100: + slotwidth = w / sum(widths) * (length - (len(widths) + 1) * self.thickness) + else: + slotwidth = w / 100 * (length - (len(widths) + 1) * self.thickness) + slot_x += slotwidth / 2 + with self.saved_context(): + self.moveTo((self.settings.height / 2) + extra_height, slot_x, 0) + self.rectangularHole(0,0,slot_height,slotwidth,slot_height/2,True,True) + slot_x += slotwidth / 2 + slot_offset + self.thickness + slot_offset + + self.edge(self.settings.height - r + extra_height, tabs=1) + self.corner(90, r, tabs=1) + self.edge(length - 2 * r, tabs=1) + self.corner(90, r, tabs=1) + self.edge(self.settings.height - r + extra_height, tabs=1) + + if self.settings.outset: + self.polyline(0, 90, self.settings.outset, -180) + else: + self.corner(-90) + +
[docs] def margin(self): + return self.settings.height
+ +
[docs]class HandleHoleEdge(HandleEdge): + """Extends an 'edge' by adding a rounded bumpout with optional holes and holes for parallel finger joint""" + description = "Handle with holes for parallel finger joint" + char = "Y" + extra_height = 1.0 + + def __call__(self, length, **kw): + self.fingerHolesAt(0, -0.5*self.settings.thickness, length, 0) + super().__call__(length, **kw) + +
[docs] def margin(self): + return self.settings.height + self.extra_height*self.settings.thickness
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/formats.html b/html/_modules/boxes/formats.html new file mode 100644 index 0000000..e04f578 --- /dev/null +++ b/html/_modules/boxes/formats.html @@ -0,0 +1,179 @@ + + + + + + + + boxes.formats — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.formats

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+import subprocess
+import tempfile
+import os
+import shutil
+from boxes.drawing import SVGSurface, PSSurface, LBRN2Surface, Context
+
+
[docs]class Formats: + + pstoedit_candidates = ["/usr/bin/pstoedit", "pstoedit", "pstoedit.exe"] + + _BASE_FORMATS = ['svg', 'svg_Ponoko', 'ps', 'lbrn2'] + + formats = { + "svg": None, + "svg_Ponoko": None, + "ps": None, + "lbrn2": None, + "dxf": "-flat 0.1 -f dxf:-mm".split(), + "gcode": "-f gcode".split(), + "plt": "-f plot-hpgl".split(), + "ai": "-f ps2ai".split(), + "pdf": "-f pdf".split(), + } + + http_headers = { + "svg": [('Content-type', 'image/svg+xml; charset=utf-8')], + "svg_Ponoko": [('Content-type', 'image/svg+xml; charset=utf-8')], + "ps": [('Content-type', 'application/postscript')], + "lbrn2": [('Content-type', 'application/lbrn2')], + "dxf": [('Content-type', 'image/vnd.dxf')], + "plt": [('Content-type', ' application/vnd.hp-hpgl')], + "gcode": [('Content-type', 'text/plain; charset=utf-8')], + + # "" : [('Content-type', '')], + } + + def __init__(self): + for cmd in self.pstoedit_candidates: + self.pstoedit = shutil.which(cmd) + if self.pstoedit: + break + +
[docs] def getFormats(self): + if self.pstoedit: + return sorted(self.formats.keys()) + return self._BASE_FORMATS
+ +
[docs] def getSurface(self, fmt, filename): + if fmt in ("svg", "svg_Ponoko"): + surface = SVGSurface(filename) + elif fmt == "lbrn2": + surface = LBRN2Surface(filename) + else: + surface = PSSurface(filename) + + ctx = Context(surface) + return surface, ctx
+ +
[docs] def convert(self, filename, fmt, metadata=None): + + if fmt not in self._BASE_FORMATS: + fd, tmpfile = tempfile.mkstemp(dir=os.path.dirname(filename)) + cmd = [self.pstoedit] + self.formats[fmt] + [filename, tmpfile] + err = subprocess.call(cmd) + + if err: + # XXX show stderr output + try: + os.unlink(tmpfile) + except: + pass + raise ValueError("Conversion failed. pstoedit returned %i" % err) + + os.rename(tmpfile, filename)
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/gears.html b/html/_modules/boxes/gears.html new file mode 100644 index 0000000..dc31bad --- /dev/null +++ b/html/_modules/boxes/gears.html @@ -0,0 +1,828 @@ + + + + + + + + boxes.gears — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.gears

+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+""
+
+'''
+Copyright (C) 2007 Aaron Spike  (aaron @ ekips.org)
+Copyright (C) 2007 Tavmjong Bah (tavmjong @ free.fr)
+Copyright (C) http://cnc-club.ru/forum/viewtopic.php?f=33&t=434&p=2594#p2500
+Copyright (C) 2014 Jürgen Weigert (juewei@fabmail.org)
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+2014-03-20 jw@suse.de 0.2  Option --accuracy=0 for automatic added.
+2014-03-21                 sent upstream: https://bugs.launchpad.net/inkscape/+bug/1295641
+2014-03-21 jw@suse.de 0.3  Fixed center of rotation for gears with odd number of teeth.
+2014-04-04 juewei     0.7  Revamped calc_unit_factor(). 
+2014-04-05 juewei    0.7a  Correctly positioned rack gear.
+                       The geometry above the meshing line is wrong.
+2014-04-06 juewei    0.7b  Undercut detection added. Reference:
+               http://nptel.ac.in/courses/IIT-MADRAS/Machine_Design_II/pdf/2_2.pdf
+               Manually merged https://github.com/jnweiger/inkscape-gears-dev/pull/15
+2014-04-07 juewei    0.7c  Manually merged https://github.com/jnweiger/inkscape-gears-dev/pull/17
+2014-04-09 juewei    0.8   Fixed https://github.com/jnweiger/inkscape-gears-dev/issues/19
+			   Ring gears are ready for production now. Thanks neon22 for driving this.
+			   Profile shift implemented (Advanced Tab), fixing 
+			   https://github.com/jnweiger/inkscape-gears-dev/issues/9
+2015-05-29 juewei 0.9 	ported to inkscape 0.91
+			AttributeError: 'module' object inkex has no attribute 'uutounit
+			Fixed https://github.com/jnweiger/inkscape-gears-dev
+'''
+
+from os import devnull # for debugging
+from math import pi, cos, sin, tan, radians, degrees, ceil, asin, acos, sqrt
+two_pi = 2 * pi
+import argparse
+from boxes.vectors import kerf, vdiff, vlength
+
+__version__ = '0.9'
+
+
[docs]def linspace(a,b,n): + """ return list of linear interp of a to b in n steps + - if a and b are ints - you'll get an int result. + - n must be an integer + """ + return [a+x*(b-a)/(n-1) for x in range(0,n)]
+ +
[docs]def involute_intersect_angle(Rb, R): + " " + Rb, R = float(Rb), float(R) + return (sqrt(R**2 - Rb**2) / (Rb)) - (acos(Rb / R))
+ +
[docs]def point_on_circle(radius, angle): + " return xy coord of the point at distance radius from origin at angle " + x = radius * cos(angle) + y = radius * sin(angle) + return (x, y)
+ +### Undercut support functions +
[docs]def undercut_min_teeth(pitch_angle, k=1.0): + """ + computes the minimum tooth count for a + spur gear so that no undercut with the given pitch_angle (in deg) + and an addendum = k * metric_module, where 0 < k < 1 + + Note: + The return value should be rounded upwards for perfect safety. E.g. + min_teeth = int(math.ceil(undercut_min_teeth(20.0))) # 18, not 17 + """ + x = max(sin(radians(pitch_angle)), 0.01) + return 2*k /(x*x)
+ +
[docs]def undercut_max_k(teeth, pitch_angle=20.0): + """ computes the maximum k value for a given teeth count and pitch_angle + so that no undercut occurs. + """ + x = max(sin(radians(pitch_angle)), 0.01) + return 0.5 * teeth * x * x
+ +
[docs]def undercut_min_angle(teeth, k=1.0): + """ computes the minimum pitch angle, to that the given teeth count (and + profile shift) cause no undercut. + """ + return degrees(asin(min(0.856, sqrt(2.0*k/teeth)))) # max 59.9 deg
+ + +
[docs]def have_undercut(teeth, pitch_angle=20.0, k=1.0): + """ returns true if the specified number of teeth would + cause an undercut. + """ + return (teeth < undercut_min_teeth(pitch_angle, k))
+ + +## gather all basic gear calculations in one place +
[docs]def gear_calculations(num_teeth, circular_pitch, pressure_angle, clearance=0, ring_gear=False, profile_shift=0.): + """ Put base calcs for spur/ring gears in one place. + - negative profile shifting helps against undercut. + """ + diametral_pitch = pi / circular_pitch + pitch_diameter = num_teeth / diametral_pitch + pitch_radius = pitch_diameter / 2.0 + addendum = 1 / diametral_pitch + dedendum = addendum + dedendum *= 1+profile_shift + addendum *= 1-profile_shift + + if ring_gear: + addendum = addendum + clearance # our method + else: + dedendum = dedendum + clearance # our method + + base_radius = pitch_diameter * cos(radians(pressure_angle)) / 2.0 + outer_radius = pitch_radius + addendum + root_radius = pitch_radius - dedendum + + # Tooth thickness: Tooth width along pitch circle. + tooth_thickness = ( pi * pitch_diameter ) / ( 2.0 * num_teeth ) + + return (pitch_radius, base_radius, + addendum, dedendum, outer_radius, root_radius, + tooth_thickness + )
+ + +
[docs]def generate_rack_points(tooth_count, pitch, addendum, pressure_angle, + base_height, tab_length, clearance=0, draw_guides=False): + """ Return path (suitable for svg) of the Rack gear. + - rack gear uses straight sides + + - involute on a circle of infinite radius is a simple linear ramp + + - the meshing circle touches at y = 0, + - the highest elevation of the teeth is at y = +addendum + - the lowest elevation of the teeth is at y = -addendum-clearance + - the base_height extends downwards from the lowest elevation. + - we generate this middle tooth exactly centered on the y=0 line. + (one extra tooth on the right hand side, if number of teeth is even) + """ + spacing = 0.5 * pitch # rolling one pitch distance on the spur gear pitch_diameter. + + # roughly center rack in drawing, exact position is so that it meshes + # nicely with the spur gear. + # -0.5*spacing has a gap in the center. + # +0.5*spacing has a tooth in the center. + + if tab_length <= 0.0: + tab_length = 1E-8 + + tas = tan(radians(pressure_angle)) * addendum + tasc = tan(radians(pressure_angle)) * (addendum+clearance) + base_top = addendum+clearance + base_bot = addendum+clearance+base_height + + x_lhs = -pitch * 0.5*tooth_count - tab_length + # Start with base tab on LHS + points = [] # make list of points + points.append((x_lhs, base_bot)) + points.append((x_lhs, base_top)) + x = x_lhs + tab_length+tasc + + # An involute on a circle of infinite radius is a simple linear ramp. + # We need to add curve at bottom and use clearance. + for i in range(tooth_count): + # move along path, generating the next 'tooth' + # pitch line is at y=0. the left edge hits the pitch line at x + points.append((x-tasc, base_top)) + points.append((x+tas, -addendum)) + points.append((x+spacing-tas, -addendum)) + points.append((x+spacing+tasc, base_top)) + x += pitch + + # add base on RHS + x_rhs = x - tasc + tab_length + points.append((x_rhs, base_top)) + points.append((x_rhs, base_bot)) + # We don't close the path here. Caller does it. + # points.append((x_lhs, base_bot)) + + # Draw line representing the pitch circle of infinite diameter + guide_path = None + p = [] + if draw_guides: + p.append( (x_lhs + 0.5 * tab_length, 0) ) + p.append( (x_rhs - 0.5 * tab_length, 0) ) + + return (points, p)
+ + +
[docs]def generate_spur_points(teeth, base_radius, pitch_radius, outer_radius, root_radius, accuracy_involute, accuracy_circular): + """ given a set of core gear params + - generate the svg path for the gear + """ + half_thick_angle = two_pi / (4.0 * teeth ) #?? = pi / (2.0 * teeth) + pitch_to_base_angle = involute_intersect_angle( base_radius, pitch_radius ) + pitch_to_outer_angle = involute_intersect_angle( base_radius, outer_radius ) - pitch_to_base_angle + + start_involute_radius = max(base_radius, root_radius) + radii = linspace(start_involute_radius, outer_radius, accuracy_involute) + angles = [involute_intersect_angle(base_radius, r) for r in radii] + + centers = [(x * two_pi / float( teeth) ) for x in range( teeth ) ] + points = [] + + for c in centers: + # Angles + pitch1 = c - half_thick_angle + base1 = pitch1 - pitch_to_base_angle + offsetangles1 = [ base1 + x for x in angles] + points1 = [ point_on_circle( radii[i], offsetangles1[i]) for i in range(0,len(radii)) ] + + pitch2 = c + half_thick_angle + base2 = pitch2 + pitch_to_base_angle + offsetangles2 = [ base2 - x for x in angles] + points2 = [ point_on_circle( radii[i], offsetangles2[i]) for i in range(0,len(radii)) ] + + points_on_outer_radius = [ point_on_circle(outer_radius, x) for x in linspace(offsetangles1[-1], offsetangles2[-1], accuracy_circular) ] + + if root_radius > base_radius: + pitch_to_root_angle = pitch_to_base_angle - involute_intersect_angle(base_radius, root_radius ) + root1 = pitch1 - pitch_to_root_angle + root2 = pitch2 + pitch_to_root_angle + points_on_root = [point_on_circle (root_radius, x) for x in linspace(root2, root1+(two_pi/float(teeth)), accuracy_circular) ] + p_tmp = points1 + points_on_outer_radius[1:-1] + points2[::-1] + points_on_root[1:-1] # [::-1] reverses list; [1:-1] removes first and last element + else: + points_on_root = [point_on_circle (root_radius, x) for x in linspace(base2, base1+(two_pi/float(teeth)), accuracy_circular) ] + p_tmp = points1 + points_on_outer_radius[1:-1] + points2[::-1] + points_on_root # [::-1] reverses list + + points.extend( p_tmp ) + + return (points)
+ +
[docs]def inkbool(val): + return val not in ("False", False, "0", 0, "None", None)
+ +
[docs]class OptionParser(argparse.ArgumentParser): + + types = { + "int" : int, + "float" : float, + "string" : str, + "inkbool" : inkbool, + } + +
[docs] def add_option(self, short, long_, **kw): + kw["type"] = self.types[kw["type"]] + names = [] + if short: + names.append("-" + short.replace("-", "_")[1:]) + if long_: + names.append("--" + long_.replace("-", "_")[2:]) + self.add_argument(*names, **kw)
+ +
[docs]class Gears(): + + def __init__(self, boxes, **kw): + # an alternate way to get debug info: + # could use inkex.debug(string) instead... + try: + self.tty = open("/dev/tty", 'w') + except: + self.tty = open(devnull, 'w') # '/dev/null' for POSIX, 'nul' for Windows. + # print >>self.tty, "gears-dev " + __version__ + + self.boxes = boxes + self.OptionParser = OptionParser() + self.OptionParser.add_option("-t", "--teeth", + action="store", type="int", + dest="teeth", default=24, + help="Number of teeth") + + self.OptionParser.add_option("-s", "--system", + action="store", type="string", + dest="system", default='MM', + help="Select system: 'CP' (Cyclic Pitch (default)), 'DP' (Diametral Pitch), 'MM' (Metric Module)") + + self.OptionParser.add_option("-d", "--dimension", + action="store", type="float", + dest="dimension", default=1.0, + help="Tooth size, depending on system (which defaults to CP)") + + + self.OptionParser.add_option("-a", "--angle", + action="store", type="float", + dest="angle", default=20.0, + help="Pressure Angle (common values: 14.5, 20, 25 degrees)") + + self.OptionParser.add_option("-p", "--profile-shift", + action="store", type="float", + dest="profile_shift", default=20.0, + help="Profile shift [in percent of the module]. Negative values help against undercut") + + self.OptionParser.add_option("-u", "--units", + action="store", type="string", + dest="units", default='mm', + help="Units this dialog is using") + + self.OptionParser.add_option("-A", "--accuracy", + action="store", type="int", + dest="accuracy", default=0, + help="Accuracy of involute: automatic: 5..20 (default), best: 20(default), medium 10, low: 5; good acuracy is important with a low tooth count") + # Clearance: Radial distance between top of tooth on one gear to bottom of gap on another. + self.OptionParser.add_option("", "--clearance", + action="store", type="float", + dest="clearance", default=0.0, + help="Clearance between bottom of gap of this gear and top of tooth of another") + + self.OptionParser.add_option("", "--annotation", + action="store", type="inkbool", + dest="annotation", default=False, + help="Draw annotation text") + + self.OptionParser.add_option("-i", "--internal-ring", + action="store", type="inkbool", + dest="internal_ring", default=False, + help="Ring (or Internal) gear style (default: normal spur gear)") + + self.OptionParser.add_option("", "--mount-hole", + action="store", type="float", + dest="mount_hole", default=0., + help="Mount hole diameter") + + self.OptionParser.add_option("", "--mount-diameter", + action="store", type="float", + dest="mount_diameter", default=15, + help="Mount support diameter") + + self.OptionParser.add_option("", "--spoke-count", + action="store", type="int", + dest="spoke_count", default=3, + help="Spokes count") + + self.OptionParser.add_option("", "--spoke-width", + action="store", type="float", + dest="spoke_width", default=5, + help="Spoke width") + + self.OptionParser.add_option("", "--holes-rounding", + action="store", type="float", + dest="holes_rounding", default=5, + help="Holes rounding") + + self.OptionParser.add_option("", "--active-tab", + action="store", type="string", + dest="active_tab", default='', + help="Active tab. Not used now.") + + self.OptionParser.add_option("-x", "--centercross", + action="store", type="inkbool", + dest="centercross", default=False, + help="Draw cross in center") + + self.OptionParser.add_option("-c", "--pitchcircle", + action="store", type="inkbool", + dest="pitchcircle", default=False, + help="Draw pitch circle (for mating)") + + self.OptionParser.add_option("-r", "--draw-rack", + action="store", type="inkbool", + dest="drawrack", default=False, + help="Draw rack gear instead of spur gear") + + self.OptionParser.add_option("", "--rack-teeth-length", + action="store", type="int", + dest="teeth_length", default=12, + help="Length (in teeth) of rack") + + self.OptionParser.add_option("", "--rack-base-height", + action="store", type="float", + dest="base_height", default=8, + help="Height of base of rack") + + self.OptionParser.add_option("", "--rack-base-tab", + action="store", type="float", + dest="base_tab", default=14, + help="Length of tabs on ends of rack") + + self.OptionParser.add_option("", "--undercut-alert", + action="store", type="inkbool", + dest="undercut_alert", default=False, + help="Let the user confirm a warning dialog if undercut occurs. This dialog also shows helpful hints against undercut") + +
[docs] def drawPoints(self, lines, kerfdir=1, close=True): + + if not lines: + return + + if kerfdir != 0: + lines = kerf(lines, self.boxes.burn*kerfdir, closed=close) + + self.boxes.ctx.save() + self.boxes.ctx.move_to(*lines[0]) + + for x, y in lines[1:]: + self.boxes.ctx.line_to(x, y) + + if close: + self.boxes.ctx.line_to(*lines[0]) + self.boxes.ctx.restore()
+ +
[docs] def calc_circular_pitch(self): + """ We use math based on circular pitch. + """ + dimension = self.options.dimension + if self.options.system == 'CP': # circular pitch + circular_pitch = dimension * 25.4 + elif self.options.system == 'DP': # diametral pitch + circular_pitch = pi * 25.4 / dimension + elif self.options.system == 'MM': # module (metric) + circular_pitch = pi * dimension + else: + raise ValueError("unknown system '%s', try CP, DP, MM" % self.options.system) + + # circular_pitch defines the size in mm + return circular_pitch
+ +
[docs] def generate_spokes(self, root_radius, spoke_width, spokes, mount_radius, mount_hole, + unit_factor, unit_label): + """ given a set of constraints + - generate the svg path for the gear spokes + - lies between mount_radius (inner hole) and root_radius (bottom of the teeth) + - spoke width also defines the spacing at the root_radius + - mount_radius is adjusted so that spokes fit if there is room + - if no room (collision) then spokes not drawn + """ + + if not spokes: + return [] + + # Spokes + collision = False # assume we draw spokes + messages = [] # messages to send back about changes. + spoke_holes = [] + r_outer = root_radius - spoke_width + + try: + spoke_count = spokes + spokes = [i*2*pi/spokes for i in range(spoke_count)] + except TypeError: + spoke_count = len(spokes) + spokes = [radians(a) for a in spokes] + spokes.append(spokes[0]+two_pi) + + # checks for collision with spokes + # check for mount hole collision with inner spokes + if mount_radius <= mount_hole/2: + adj_factor = (r_outer - mount_hole/2) / 5 + + if adj_factor < 0.1: + # not enough reasonable room + collision = True + else: + mount_radius = mount_hole/2 + adj_factor # small fix + messages.append("Mount support too small. Auto increased to %2.2f%s." % (mount_radius/unit_factor*2, unit_label)) + + # then check to see if cross-over on spoke width + for i in range(spoke_count): + angle = spokes[i]-spokes[i-1] + + if spoke_width >= angle * mount_radius: + adj_factor = 1.2 # wrong value. its probably one of the points distances calculated below + mount_radius += adj_factor + messages.append("Too many spokes. Increased Mount support by %2.3f%s" % (adj_factor/unit_factor, unit_label)) + + # check for collision with outer rim + if r_outer <= mount_radius: + # not enough room to draw spokes so cancel + collision = True + if collision: # don't draw spokes if no room. + messages.append("Not enough room for Spokes. Decrease Spoke width.") + else: # draw spokes + + for i in range(spoke_count): + self.boxes.ctx.save() + start_a, end_a = spokes[i], spokes[i+1] + # inner circle around mount + asin_factor = spoke_width/mount_radius/2 + # check if need to clamp radius + asin_factor = max(-1.0, min(1.0, asin_factor)) # no longer needed - resized above + a = asin(asin_factor) + + # is inner circle too small + asin_factor = spoke_width/r_outer/2 + # check if need to clamp radius + asin_factor = max(-1.0, min(1.0, asin_factor)) # no longer needed - resized above + a2 = asin(asin_factor) + l = vlength(vdiff(point_on_circle(mount_radius, start_a + a), + point_on_circle(r_outer, start_a + a2))) + self.boxes.moveTo(*point_on_circle(mount_radius, start_a + a), degrees=degrees(start_a)) + self.boxes.polyline( + l, + +90+degrees(a2), 0, + (degrees(end_a-start_a-2*a2), r_outer), 0, + +90+degrees(a2), + l, 90-degrees(a), 0, + (-degrees(end_a-start_a-2*a), mount_radius), + 0, 90+degrees(a2), 0 + ) + + self.boxes.ctx.restore() + + return messages
+ +
[docs] def sizes(self, **kw): + self.options = self.OptionParser.parse_args(["--%s=%s" % (name,value) for name, value in kw.items()]) + # Pitch (circular pitch): Length of the arc from one tooth to the next) + # Pitch diameter: Diameter of pitch circle. + pitch = self.calc_circular_pitch() + + if self.options.drawrack: + base_height = self.options.base_height * unit_factor + tab_width = self.options.base_tab * unit_factor + tooth_count = self.options.teeth_length + width = tooth_count * pitch + 2*tab_width + height = base_height+ 2* addendum + return 0, width, height + + teeth = self.options.teeth + # Angle of tangent to tooth at circular pitch wrt radial line. + angle = self.options.angle + # Clearance: Radial distance between top of tooth on one gear to + # bottom of gap on another. + clearance = self.options.clearance # * unit_factor + # Replace section below with this call to get the combined gear_calculations() above + (pitch_radius, base_radius, addendum, dedendum, + outer_radius, root_radius, tooth) = gear_calculations(teeth, pitch, angle, clearance, self.options.internal_ring, self.options.profile_shift*0.01) + if self.options.internal_ring: + outer_radius += self.options.spoke_width + return pitch_radius, 2*outer_radius, 2*outer_radius
+ +
[docs] def gearCarrier(self, r, spoke_width, positions, mount_radius, mount_hole, circle=True, callback=None, move=None): + width = 2*r+spoke_width + + if self.boxes.move(width, width, move, before=True): + return + + try: + positions = [i*360/positions for i in range(positions)] + except TypeError: + pass + + self.boxes.ctx.save() + self.boxes.moveTo(width/2.0, width/2.0) + if callback: + self.boxes.cc(callback, None) + self.generate_spokes(r+0.5*spoke_width, spoke_width, positions, mount_radius, mount_hole, 1, "") + self.boxes.hole(0, 0, mount_hole) + + for angle in positions: + self.boxes.ctx.save() + self.boxes.moveTo(0, 0, angle) + self.boxes.hole(r, 0, mount_hole) + self.boxes.ctx.restore() + + self.boxes.moveTo(r+0.5*spoke_width+self.boxes.burn, 0, 90) + self.boxes.corner(360, r+0.5*spoke_width) + + self.boxes.ctx.restore() + self.boxes.move(width, width, move)
+ + def __call__(self, teeth_only=False, move="", callback=None, **kw): + """ Calculate Gear factors from inputs. + - Make list of radii, angles, and centers for each tooth and + iterate through them + - Turn on other visual features e.g. cross, rack, annotations, etc + """ + self.options = self.OptionParser.parse_args(["--%s=%s" % (name,value) for name, value in kw.items()]) + + warnings = [] # list of extra messages to be shown in annotations + # calculate unit factor for units defined in dialog. + unit_factor = 1 + # User defined options + teeth = self.options.teeth + # Angle of tangent to tooth at circular pitch wrt radial line. + angle = self.options.angle + # Clearance: Radial distance between top of tooth on one gear to + # bottom of gap on another. + clearance = self.options.clearance * unit_factor + mount_hole = self.options.mount_hole * unit_factor + # for spokes + mount_radius = self.options.mount_diameter * 0.5 * unit_factor + spoke_count = self.options.spoke_count + spoke_width = self.options.spoke_width * unit_factor + holes_rounding = self.options.holes_rounding * unit_factor # unused + # visible guide lines + centercross = self.options.centercross # draw center or not (boolean) + pitchcircle = self.options.pitchcircle # draw pitch circle or not (boolean) + + # Accuracy of teeth curves + accuracy_involute = 20 # Number of points of the involute curve + accuracy_circular = 9 # Number of points on circular parts + if self.options.accuracy is not None: + if self.options.accuracy == 0: + # automatic + if teeth < 10: accuracy_involute = 20 + elif teeth < 30: accuracy_involute = 12 + else: accuracy_involute = 6 + else: + accuracy_involute = self.options.accuracy + + accuracy_circular = max(3, int(accuracy_involute/2) - 1) # never less than three + # print >>self.tty, "accuracy_circular=%s accuracy_involute=%s" % (accuracy_circular, accuracy_involute) + # Pitch (circular pitch): Length of the arc from one tooth to the next) + # Pitch diameter: Diameter of pitch circle. + pitch = self.calc_circular_pitch() + # Replace section below with this call to get the combined gear_calculations() above + (pitch_radius, base_radius, addendum, dedendum, + outer_radius, root_radius, tooth) = gear_calculations(teeth, pitch, angle, clearance, self.options.internal_ring, self.options.profile_shift*0.01) + + b = self.boxes.burn + # Add Rack (instead) + if self.options.drawrack: + base_height = self.options.base_height * unit_factor + tab_width = self.options.base_tab * unit_factor + tooth_count = self.options.teeth_length + (points, guide_points) = generate_rack_points(tooth_count, pitch, addendum, angle, + base_height, tab_width, clearance, pitchcircle) + width = tooth_count * pitch + 2 * tab_width + height = base_height + 2 * addendum + if self.boxes.move(width, height, move, before=True): + return + + self.boxes.cc(callback, None) + self.boxes.moveTo(width/2.0, base_height+addendum, -180) + if base_height < 0: + points = points[1:-1] + self.drawPoints(points, close=base_height >= 0) + self.drawPoints(guide_points, kerfdir=0) + self.boxes.move(width, height, move) + + return + + # Move only + width = height = 2 * outer_radius + if self.options.internal_ring: + width = height = width + 2 * self.options.spoke_width + + if not teeth_only and self.boxes.move(width, height, move, before=True): + return + + # Detect Undercut of teeth +## undercut = int(ceil(undercut_min_teeth( angle ))) +## needs_undercut = teeth < undercut #? no longer needed ? + if have_undercut(teeth, angle, 1.0): + min_teeth = int(ceil(undercut_min_teeth(angle, 1.0))) + min_angle = undercut_min_angle(teeth, 1.0) + .1 + max_k = undercut_max_k(teeth, angle) + msg = "Undercut Warning: This gear (%d teeth) will not work well.\nTry tooth count of %d or more,\nor a pressure angle of %.1f [deg] or more,\nor try a profile shift of %d %%.\nOr other decent combinations." % (teeth, min_teeth, min_angle, int(100.*max_k)-100.) + # alas annotation cannot handle the degree symbol. Also it ignore newlines. + # so split and make a list + warnings.extend(msg.split("\n")) + + # All base calcs done. Start building gear + points = generate_spur_points(teeth, base_radius, pitch_radius, outer_radius, root_radius, accuracy_involute, accuracy_circular) + + if not teeth_only: + self.boxes.moveTo(width/2, height/2) + self.boxes.cc(callback, None, 0, 0) + self.drawPoints(points) + # Spokes + if not teeth_only and not self.options.internal_ring: # only draw internals if spur gear + msg = self.generate_spokes(root_radius, spoke_width, spoke_count, mount_radius, mount_hole, + unit_factor, self.options.units) + warnings.extend(msg) + + # Draw mount hole + # A : rx,ry x-axis-rotation, large-arch-flag, sweepflag x,y + r = mount_hole / 2 + self.boxes.hole(0, 0, r) + elif not teeth_only: + # its a ring gear + # which only has an outer ring where width = spoke width + r = outer_radius + spoke_width + self.boxes.burn + self.boxes.ctx.save() + self.boxes.moveTo(r, 0) + self.boxes.ctx.arc(-r, 0, r, 0, 2*pi) + self.boxes.ctx.restore() + + # Add center + if centercross: + cs = pitch / 3.0 # centercross length + self.boxes.ctx.save() + self.boxes.ctx.move_to(-cs, 0) + self.boxes.ctx.line_to(+cs, 0) + self.boxes.ctx.move_to(0, -cs) + self.boxes.ctx.line_to(0, +cs) + self.boxes.ctx.restore() + + # Add pitch circle (for mating) + if pitchcircle: + self.boxes.hole(0, 0, pitch_radius) + + # Add Annotations (above) + if self.options.annotation: + outer_dia = outer_radius * 2 + + if self.options.internal_ring: + outer_dia += 2 * spoke_width + + notes = [] + notes.extend(warnings) + #notes.append('Document (%s) scale conversion = %2.4f' % (self.document.getroot().find(inkex.addNS('namedview', 'sodipodi')).get(inkex.addNS('document-units', 'inkscape')), unit_factor)) + notes.extend(['Teeth: %d CP: %2.4f(%s) ' % (teeth, pitch / unit_factor, self.options.units), + 'DP: %2.3f Module: %2.4f(mm)' % (25.4 * pi / pitch, pitch), + 'Pressure Angle: %2.2f degrees' % (angle), + 'Pitch diameter: %2.3f %s' % (pitch_radius * 2 / unit_factor, self.options.units), + 'Outer diameter: %2.3f %s' % (outer_dia / unit_factor, self.options.units), + 'Base diameter: %2.3f %s' % (base_radius * 2 / unit_factor, self.options.units)#, + #'Addendum: %2.4f %s' % (addendum / unit_factor, self.options.units), + #'Dedendum: %2.4f %s' % (dedendum / unit_factor, self.options.units) + ]) + # text height relative to gear size. + # ranges from 10 to 22 over outer radius size 60 to 360 + text_height = max(10, min(10+(outer_dia-60)/24, 22)) + # position above + y = - outer_radius - (len(notes)+1) * text_height * 1.2 + + for note in notes: + self.boxes.text(note, -outer_radius, y) + y += text_height * 1.2 + + if not teeth_only: + self.boxes.move(width, height, move)
+ +if __name__ == '__main__': + e = Gears() + e.affect() + +# Notes + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators.html b/html/_modules/boxes/generators.html new file mode 100644 index 0000000..8cd88e8 --- /dev/null +++ b/html/_modules/boxes/generators.html @@ -0,0 +1,156 @@ + + + + + + + + boxes.generators — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators

+import pkgutil
+import inspect
+import importlib
+import boxes
+
+ui_groups_by_name = {}
+
+
[docs]class UIGroup: + + def __init__(self, name, title=None, description="", image=""): + self.name = name + self.title = title or name + self.description = description + self._image = image + self.generators = [] + # register + ui_groups_by_name[name] = self + +
[docs] def add(self, box): + self.generators.append(box) + self.generators.sort(key=lambda b:getattr(b, '__name__', None) or b.__class__.__name__)
+ + @property + def thumbnail(self): + return self._image and f"{self._image}-thumb.jpg" + + @property + def image(self): + return self._image and f"{self._image}.jpg"
+ +ui_groups = [ + UIGroup("Box", "Boxes", image="UniversalBox"), + UIGroup("FlexBox", "Boxes with flex", image="RoundedBox"), + UIGroup("Tray", "Trays and Drawer Inserts", image="TypeTray"), + UIGroup("Shelf", "Shelves", image="DisplayShelf"), + UIGroup("WallMounted", image="WallTypeTray"), + UIGroup("Holes", "Hole patterns", image=""), + UIGroup("Part", "Parts and Samples", image="BurnTest"), + UIGroup("Misc", image="TrafficLight"), + UIGroup("Unstable", description="Generators are still untested or need manual adjustment to be useful."), + ] + +
[docs]def getAllBoxGenerators(): + generators = {} + for importer, modname, ispkg in pkgutil.walk_packages( + path=__path__, + prefix=__name__+'.'): + module = importlib.import_module(modname) + if module.__name__.split('.')[-1].startswith("_"): + continue + for k, v in module.__dict__.items(): + if v is boxes.Boxes: + continue + if (inspect.isclass(v) and issubclass(v, boxes.Boxes) and + v.__name__[0] != '_'): + generators[modname + '.' + v.__name__] = v + return generators
+ +
[docs]def getAllGeneratorModules(): + generators = {} + for importer, modname, ispkg in pkgutil.walk_packages( + path=__path__, + prefix=__name__+'.', + onerror=lambda x: None): + module = importlib.import_module(modname) + generators[modname.split('.')[-1]] = module + return generators
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/_template.html b/html/_modules/boxes/generators/_template.html new file mode 100644 index 0000000..0109f2e --- /dev/null +++ b/html/_modules/boxes/generators/_template.html @@ -0,0 +1,148 @@ + + + + + + + + boxes.generators._template — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators._template

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+class BOX(Boxes): # Change class name!
+    """DESCRIPTION"""
+
+    ui_group = "Unstable" # see ./__init__.py for names
+
+
[docs] def __init__(self): + Boxes.__init__(self) + + # Uncomment the settings for the edge types you use + # use keyword args to set default values + # self.addSettingsArgs(edges.FingerJointSettings, finger=1.0,space=1.0) + # self.addSettingsArgs(edges.StackableSettings) + # self.addSettingsArgs(edges.HingeSettings) + # self.addSettingsArgs(edges.LidSettings) + # self.addSettingsArgs(edges.ClickSettings) + # self.addSettingsArgs(edges.FlexSettings) + + # remove cli params you do not need + self.buildArgParser(x=100, sx="3*50", y=100, sy="3*50", h=100, hi=0) + # Add non default cli params if needed (see argparse std lib) + self.argparser.add_argument( + "--XX", action="store", type=float, default=0.5, + help="DESCRIPTION")
+ + + def render(self): + # adjust to the variables you want in the local scope + x, y, h = self.x, self.y, self.h + t = self.thickness + + # Create new Edges here if needed E.g.: + s = edges.FingerJointSettings(self.thickness, relative=False, + space = 10, finger=10, + width=self.thickness) + p = edges.FingerJointEdge(self, s) + p.char = "a" # 'a', 'A', 'b' and 'B' is reserved for beeing used within generators + self.addPart(p) + + # render your parts here + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/abox.html b/html/_modules/boxes/generators/abox.html new file mode 100644 index 0000000..d7b044f --- /dev/null +++ b/html/_modules/boxes/generators/abox.html @@ -0,0 +1,155 @@ + + + + + + + + boxes.generators.abox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.abox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class ABox(Boxes): + """A simple Box""" + + description = "This box is kept simple on purpose. If you need more features have a look at the UniversalBox." + + ui_group = "Box" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.buildArgParser("x", "y", "h", "outside", "bottom_edge") + #self.argparser.add_argument( + # "--lid", action="store", type=str, default="default (none)", + # choices=("default (none)", "chest", "flat"), + # help="additional lid (for straight top_edge only)") + + def render(self): + x, y, h = self.x, self.y, self.h + t = self.thickness + + t1, t2, t3, t4 = "eeee" + b = self.edges.get(self.bottom_edge, self.edges["F"]) + sideedge = "F" # if self.vertical_edges == "finger joints" else "h" + + if self.outside: + self.x = x = self.adjustSize(x, sideedge, sideedge) + self.y = y = self.adjustSize(y) + self.h = h = self.adjustSize(h, b, t1) + + with self.saved_context(): + self.rectangularWall(x, h, [b, sideedge, t1, sideedge], + ignore_widths=[1, 6], move="up") + self.rectangularWall(x, h, [b, sideedge, t3, sideedge], + ignore_widths=[1, 6], move="up") + + if self.bottom_edge != "e": + self.rectangularWall(x, y, "ffff", move="up") + #self.drawAddOnLid(x, y, self.lid) + + self.rectangularWall(x, h, [b, sideedge, t3, sideedge], + ignore_widths=[1, 6], move="right only") + self.rectangularWall(y, h, [b, "f", t2, "f"], + ignore_widths=[1, 6], move="up") + self.rectangularWall(y, h, [b, "f", t4, "f"], + ignore_widths=[1, 6], move="up")
+ + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/agricolainsert.html b/html/_modules/boxes/generators/agricolainsert.html new file mode 100644 index 0000000..7d9b755 --- /dev/null +++ b/html/_modules/boxes/generators/agricolainsert.html @@ -0,0 +1,1024 @@ + + + + + + + + boxes.generators.agricolainsert — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.agricolainsert

+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2020 Guillaume Collic
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import math
+from functools import partial
+from boxes import Boxes, edges
+from .dividertray import (
+    SlotDescriptionsGenerator,
+    DividerSlotsEdge,
+)
+
+
+
[docs]class AgricolaInsert(Boxes): + """ + Agricola Revised Edition game box insert, including some expansions. + """ + + ui_group = "Misc" + + description = """ +This insert was designed with 3 mm plywood in mind, and should work fine with +materials around this thickness. + +This is an insert for the [Agricola Revised Edition](https://boardgamegeek.com/boardgame/200680/agricola-revised-edition) +board game. It is specifically designed around the [Farmers Of The Moor expansion](https://boardgamegeek.com/boardgameexpansion/257344/agricola-farmers-moor), +and should also store the [5-6 players expansion](https://boardgamegeek.com/boardgameexpansion/210625/agricola-expansion-5-and-6-players) +(not tested, but I tried to take everything into account for it, please inform +us if you tested it). + +It can be stored inside the original game box, including the 2 expansions, +with the lid slightly raised. + +The parts of a given element are mostly generated next to each other vertically. +It should be straightforward to match them. + +Here are the different elements, from left to right in the generated file. + +#### Card tray + +The cards are all kept in a tray, with paper dividers to sort them easily. When +the tray is not full of cards, wood dividers slides in slots in order to keep +the cards from falling into the empty space. + +There should be enough space for the main game, Farmers Of The Moor, and the 5-6 +player expansion, but not much more than that. + +To keep a lower profile, the cards are at a slight angle, and the paper dividers +tabs are horizontal instead of vertical. +A small wall keeps the card against one side while the tabs protrude on the +other side, above the small wall. + +The wall with the big hole is the sloped one. It goes between the two +"comb-like" walls first, with its two small holes at the bottom. Then there is a +low-height long wall with a sloped edge which should go from the sloped wall to +the other side. You can finish the tray with the last wall at the end. + +#### Upper level trays + +4 trays with movable walls are used to store resources. They were designed to +store them in this order: + +* Stone / Vegetable / Pig / Cow +* Reed / Grain / Sheep +* Wood / Clay +* Food / Fire + +The wall would probably be better if fixed instead of movable, but I would like +to test with the 5-6 player expansion to be sure their positions are correct +with it too. + +The little feet of the movable wall should be glued. The triangles are put +horizontally, with their bases towards the sides. + +#### Lower level tray + +The lower level tray is used to store the horses. + +#### Room/Field tiles + +Two boxes are generated to store the room/field tiles. One for the wood/field, +the other for the clay/stone. They are stored with the main opening upside, but +I prefer to use them during play with this face on the side. + +#### Moor/Forest and miscellaneous tiles + +A box is generated to store the Moor/Forest tiles, and some other tiles such as +the "multiple resources" cardboard tokens. + +The Moor/Forest tiles are at the same height as the Room/Field, and the upper +level trays are directly on them. The horse box and player box are slightly +lower. This Moor/Forest box have a lowered corner (the one for the miscellaneous +tiles). Two cardboard pieces can be stored between the smaller boxes and the +upper level trays (as seen on the picture). + +Be sure to match the pieces so that the walls with smaller heights are next to +each other. + +#### Players bit boxes + +Each player has its own box where the bits of his color are stored. +The cardboard bed from Farmers Of The Moor is central to this box. + +* The fences are stored inside the bed +* The bed is placed in the box, with holes to keep it there (and to take less + height) +* The stables are stored in the two corners +* The five farmers are stored between the bed and the three walls, alternatively + head up and head down. + +During assembly, the small bars are put in the middle holes. The two bigger +holes at the ends are used for the bed feet. The bar keeps the bed from +protruding underneath. + +""" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings, surroundingspaces=1.0) + + def render(self): + player_box_height = 34.5 + player_box_inner_width = 50.5 + bigger_box_inner_height = 36.7 + row_width = 37.2 + tray_inner_height = 17 + box_width = 218 + card_tray_height = ( + self.thickness * 2 + tray_inner_height + bigger_box_inner_height + ) + card_tray_width = ( + 305.35 - player_box_inner_width * 2 - row_width * 2 - 9 * self.thickness + ) + + self.render_card_divider_tray(card_tray_height, box_width, card_tray_width) + self.render_upper_token_trays(tray_inner_height, box_width) + wood_room_box_width = 39.8 + self.render_room_box(wood_room_box_width, bigger_box_inner_height, row_width) + stone_room_box_width = 26.7 + self.render_room_box(stone_room_box_width, bigger_box_inner_height, row_width) + moor_box_length = 84.6 + self.render_moor_box( + bigger_box_inner_height, player_box_height, row_width, moor_box_length + ) + horse_box_margin = 0.5 + horse_box_length = ( + box_width + - wood_room_box_width + - stone_room_box_width + - moor_box_length + - 6 * self.thickness + - horse_box_margin + ) + self.render_horse_box(player_box_height, row_width, horse_box_length) + for _ in range(6): + self.render_player_box(player_box_height, player_box_inner_width) + + def render_card_divider_tray( + self, card_tray_height, card_tray_length, card_tray_width + ): + """ + The whole tray which contains the cards, including its dividers. + Cards are at an angle, to save height. + """ + self.ctx.save() + + tray_inner_length = card_tray_length - self.thickness + margin_for_score_sheet = 0 # 3 if you want more space for score sheet + sleeved_cards_width = 62 + margin_for_score_sheet + + rad = math.acos(card_tray_height / sleeved_cards_width) + angle = math.degrees(rad) + cos = math.cos(rad) + tan = math.tan(rad) + sin = math.sin(rad) + + slots_number = 19 + slot_depth = 30 + slot_descriptions = SlotDescriptionsGenerator().generate_all_same_angles( + [tray_inner_length / slots_number for _ in range(slots_number)], + self.thickness, + 0.2, + slot_depth, + card_tray_height, + angle, + ) + slot_descriptions.adjust_to_target_length(tray_inner_length) + + sloped_wall_height = sleeved_cards_width - self.thickness * (tan + 1 / tan) + sloped_wall_posx_at_y0 = ( + tray_inner_length - sloped_wall_height * tan - cos * self.thickness + ) + sloped_wall_posx = sloped_wall_posx_at_y0 + cos * self.thickness / 2 + sloped_wall_posy = sin * self.thickness / 2 + + dse = DividerSlotsEdge(self, slot_descriptions.descriptions) + for _ in range(2): + self.rectangularWall( + tray_inner_length, + card_tray_height, + ["e", "e", dse, "f"], + move="up", + callback=[ + partial( + lambda: self.fingerHolesAt( + sloped_wall_posx, + sloped_wall_posy, + sloped_wall_height, + angle=90 - angle, + ) + ) + ], + ) + + # generate spacer + spacer_height = card_tray_height / 2 + spacer_spacing = card_tray_width-99.8 + spacer_upper_width = sloped_wall_posx_at_y0 + spacer_height * tan + self.trapezoidWall( + spacer_height, + spacer_upper_width, + sloped_wall_posx_at_y0, + "fefe", + move="up rotated", + ) + + self.rectangularWall( + card_tray_width, + card_tray_height, + "eFeF", + move="up", + callback=[ + partial( + lambda: self.fingerHolesAt( + spacer_spacing - self.thickness / 2, 0, spacer_height + ) + ) + ], + ) + self.rectangularWall( + card_tray_width, + sloped_wall_height, + "efef", + move="up", + callback=[ + partial( + self.generate_card_tray_sloped_wall_holes, + card_tray_width, + sloped_wall_height, + spacer_height, + spacer_spacing, + rad, + ) + ], + ) + + self.ctx.restore() + self.rectangularWall(card_tray_length, 0, "FFFF", move="right only") + self.ctx.save() + + divider_height = sleeved_cards_width - self.thickness * tan + self.generate_divider( + card_tray_width, divider_height, slot_depth, spacer_spacing, "up" + ) + self.explain( + [ + "Wood divider", + "Hard separation to keep the card", + "from slipping in empty space left.", + "Takes more space, but won't move.", + "Duplicate as much as you want", + "(I use 2).", + ] + ) + + self.ctx.restore() + self.rectangularWall(card_tray_width, 0, "ffff", move="right only") + self.ctx.save() + + self.generate_paper_divider( + card_tray_width, sleeved_cards_width, slot_depth, spacer_spacing, "up" + ) + self.explain( + [ + "Paper divider", + "Soft separation to search easily", + "the card group you need", + "(by expansion, number of player,", + "etc.).", + "Duplicate as much as you want", + "(I use 7).", + ] + ) + self.ctx.restore() + self.rectangularWall(card_tray_width, 0, "ffff", move="right only") + + def explain(self, strings): + self.text( + str.join( + "\n", + strings, + ), + fontsize=7, + align="bottom left", + ) + + def generate_sloped_wall_holes(self, side_wall_length, rad, sloped_wall_height): + cos = math.cos(rad) + tan = math.tan(rad) + sin = math.sin(rad) + posx_at_y0 = side_wall_length - sloped_wall_height * tan + posx = posx_at_y0 - cos * self.thickness / 2 + posy = sin * self.thickness / 2 + self.fingerHolesAt(posx, posy, sloped_wall_height, angle=90 - math.degrees(rad)) + + def generate_card_tray_sloped_wall_holes( + self, side_wall_length, sloped_wall_height, spacer_height, spacer_spacing, rad + ): + # Spacer finger holes + self.fingerHolesAt( + side_wall_length - (spacer_spacing - self.thickness / 2), + # the sloped wall doesn't exactly touch the bottom of the spacer + -self.thickness * math.tan(rad), + spacer_height / math.cos(rad), + ) + + # Big hole to access "lost" space behind sloped wall + radius = 5 + padding = 8 + total_loss = 2 * radius + 2 * padding + self.moveTo(radius + padding, padding) + self.polyline( + side_wall_length - total_loss, + (90, radius), + sloped_wall_height - total_loss, + (90, radius), + side_wall_length - total_loss, + (90, radius), + sloped_wall_height - total_loss, + (90, radius), + ) + + def generate_paper_divider(self, width, height, slot_depth, spacer_spacing, move): + """ + A card separation made of paper, which moves freely in the card tray. + Takes less space and easy to manipulate, but won't block cards in place. + """ + if self.move(width, height, move, True): + return + + margin = 0.5 + actual_width = width - margin + self.polyline( + actual_width - spacer_spacing, + 90, + height - slot_depth, + -90, + spacer_spacing, + 90, + slot_depth, + 90, + actual_width, + 90, + height, + 90, + ) + + # Move for next piece + self.move(width, height, move) + + def generate_divider(self, width, height, slot_depth, spacer_spacing, move): + """ + A card separation made of wood which slides in the side slots. + Can be useful to do hard separations, but takes more space and + less movable than the paper ones. + """ + total_width = width + 2 * self.thickness + + if self.move(total_width, height, move, True): + return + + radius = 16 + padding = 20 + divider_notch_depth = 35 + + self.polyline( + self.thickness + spacer_spacing + padding - radius, + (90, radius), + divider_notch_depth - radius - radius, + (-90, radius), + width - 2 * radius - 2 * padding - spacer_spacing, + (-90, radius), + divider_notch_depth - radius - radius, + (90, radius), + self.thickness + padding - radius, + 90, + slot_depth, + 90, + self.thickness, + -90, + height - slot_depth, + 90, + width - spacer_spacing, + 90, + height - slot_depth, + -90, + self.thickness + spacer_spacing, + 90, + slot_depth, + ) + + # Move for next piece + self.move(total_width, height, move) + + def render_horse_box(self, player_box_height, row_width, width): + """ + Box for the horses on lower level. Same height as player boxes. + """ + length = 2 * row_width + 3 * self.thickness + self.render_simple_tray(width, length, player_box_height) + + def render_moor_box( + self, bigger_box_inner_height, player_box_height, row_width, length + ): + """ + Box for the moor/forest tiles, and the cardboard tokens with multiple + units of resources. + A corner is lowered (the one for the tokens) at the same height as player boxes + to store 2 levels of small boards there. + """ + self.ctx.save() + height = bigger_box_inner_height + lowered_height = player_box_height - self.thickness + lowered_corner_height = height - lowered_height + corner_length = 53.5 + + self.rectangularWall( + length, + 2 * row_width + self.thickness, + "FfFf", + move="up", + callback=[ + partial( + lambda: self.fingerHolesAt( + 0, row_width + 0.5 * self.thickness, length, 0 + ) + ) + ], + ) + + for i in range(2): + self.rectangularWall( + length, + lowered_height, + [ + "f", + "f", + MoorBoxSideEdge( + self, corner_length, lowered_corner_height, i % 2 == 0 + ), + "f", + ], + move="up", + ) + self.rectangularWall(length, height / 2, "ffef", move="up") + + for i in range(2): + self.rectangularWall( + 2 * row_width + self.thickness, + lowered_height, + [ + "F", + "F", + MoorBoxHoleEdge(self, height, lowered_corner_height, i % 2 == 0), + "F", + ], + move="up", + callback=[ + partial(self.generate_side_finger_holes, row_width, height / 2) + ], + ) + + self.ctx.restore() + self.rectangularWall(length, 0, "FFFF", move="right only") + + def generate_side_finger_holes(self, row_width, height): + self.fingerHolesAt(row_width + 0.5 * self.thickness, 0, height) + + def render_room_box(self, width, height, row_width): + """ + A box in which storing room/field tiles. + """ + border_height = 12 + room_box_length = row_width * 2 + self.thickness + + self.ctx.save() + + self.rectangularWall( + room_box_length, + height, + "eFfF", + move="up", + callback=[partial(self.generate_side_finger_holes, row_width, height)], + ) + + self.rectangularWall( + room_box_length, + width, + "FFfF", + move="up", + callback=[partial(self.generate_side_finger_holes, row_width, width)], + ) + + self.rectangularWall( + room_box_length, + border_height, + "FFeF", + move="up", + callback=[ + partial(self.generate_side_finger_holes, row_width, border_height) + ], + ) + + for _ in range(3): + self.trapezoidWall(width, height, border_height, "ffef", move="up") + + self.ctx.restore() + + self.rectangularWall(room_box_length, 0, "FFFF", move="right only") + + def render_player_box(self, player_box_height, player_box_inner_width): + """ + A box in which storing all the bits of a single player, + including (and designed for) the cardboard bed from Farmers Of The Moor. + """ + self.ctx.save() + bed_inner_height = player_box_height - self.thickness + bed_inner_length = 66.75 + bed_inner_width = player_box_inner_width + cardboard_bed_foot_height = 6.5 + cardboard_bed_hole_margin = 5 + cardboard_bed_hole_length = 12 + bed_head_length = 20 + bed_foot_height = 18 + support_length = 38 + + bed_edge = Bed2SidesEdge( + self, bed_inner_length, bed_head_length, bed_foot_height + ) + noop_edge = NoopEdge(self) + self.ctx.save() + optim_180_x = ( + bed_inner_length + self.thickness + bed_head_length + 2 * self.spacing + ) + optim_180_y = 2 * bed_foot_height - player_box_height + 2 * self.spacing + for _ in range(2): + self.rectangularWall( + bed_inner_length, + bed_inner_height, + ["F", bed_edge, noop_edge, "F"], + move="up", + ) + self.moveTo(optim_180_x, optim_180_y, -180) + self.ctx.restore() + self.moveTo(0, bed_inner_height + self.thickness + self.spacing + optim_180_y) + + self.rectangularWall( + bed_inner_length, + bed_inner_width, + "feff", + move="up", + callback=[ + partial( + self.generate_bed_holes, + bed_inner_width, + cardboard_bed_hole_margin, + cardboard_bed_hole_length, + support_length, + ) + ], + ) + + self.ctx.save() + self.rectangularWall( + bed_inner_width, + bed_inner_height, + ["F", "f", BedHeadEdge(self, bed_inner_height - 15), "f"], + move="right", + ) + for _ in range(2): + self.rectangularWall( + cardboard_bed_foot_height - self.thickness, + support_length, + "efee", + move="right", + ) + self.ctx.restore() + self.rectangularWall( + bed_inner_width, + bed_inner_height, + "Ffef", + move="up only", + ) + + self.ctx.restore() + self.rectangularWall( + bed_inner_length + bed_head_length + self.spacing - self.thickness, + 0, + "FFFF", + move="right only", + ) + + def generate_bed_holes(self, width, margin, hole_length, support_length): + support_start = margin + hole_length + + bed_width = 29.5 + bed_space_to_wall = (width - bed_width) / 2 + bed_feet_width = 3 + + posy_1 = bed_space_to_wall + posy_2 = width - bed_space_to_wall + + for y, direction in [(posy_1, 1), (posy_2, -1)]: + bed_feet_middle_y = y + direction * bed_feet_width / 2 + support_middle_y = y + direction * self.thickness / 2 + self.rectangularHole( + margin, + bed_feet_middle_y, + hole_length, + bed_feet_width, + center_x=False, + ) + self.fingerHolesAt(support_start, support_middle_y, support_length, angle=0) + self.rectangularHole( + support_start + support_length, + bed_feet_middle_y, + hole_length, + bed_feet_width, + center_x=False, + ) + + def render_upper_token_trays(self, tray_inner_height, box_width): + """ + Upper level : multiple trays for each ressource + (beside horses which are on the lower level) + """ + tray_height = tray_inner_height + self.thickness + upper_level_width = 196 + upper_level_length = box_width + row_width = upper_level_width / 3 + + # Stone / Vegetable / Pig / Cow + self.render_simple_tray(row_width, upper_level_length, tray_height, 3) + # Reed / Grain / Sheep + self.render_simple_tray(row_width, upper_level_length * 2 / 3, tray_height, 2) + # Wood / Clay + self.render_simple_tray(row_width, upper_level_length * 2 / 3, tray_height, 1) + # Food / Fire + self.render_simple_tray(upper_level_length / 3, row_width * 2, tray_height, 1) + + def render_simple_tray(self, outer_width, outer_length, outer_height, dividers=0): + """ + One of the upper level trays, with movable dividers. + """ + width = outer_width - 2 * self.thickness + length = outer_length - 2 * self.thickness + height = outer_height - self.thickness + self.ctx.save() + self.rectangularWall(width, length, "FFFF", move="up") + for _ in range(2): + self.rectangularWall(width, height, "ffef", move="up") + self.ctx.restore() + self.rectangularWall(width, length, "FFFF", move="right only") + for _ in range(2): + self.rectangularWall(height, length, "FfFe", move="right") + + if dividers: + self.ctx.save() + for _ in range(dividers): + self.render_simple_tray_divider(width, height, "up") + self.ctx.restore() + self.render_simple_tray_divider(width, height, "right only") + + def render_simple_tray_divider(self, width, height, move): + """ + Simple movable divider. A wall with small feet for a little more stability. + """ + + if self.move(height, width, move, True): + return + + t = self.thickness + self.polyline( + height - t, + 90, + t, + -90, + t, + 90, + width - 2 * t, + 90, + t, + -90, + t, + 90, + height - t, + 90, + width, + 90, + ) + + self.move(height, width, move) + + self.render_simple_tray_divider_feet(width, height, move) + + def render_simple_tray_divider_feet(self, width, height, move): + sqr2 = math.sqrt(2) + t = self.thickness + divider_foot_width = 2 * t + full_width = t + 2 * divider_foot_width + move_length = self.spacing + full_width / sqr2 + move_width = self.spacing + max(full_width, height) + + if self.move(move_width, move_length, move, True): + return + + self.ctx.save() + self.polyline( + sqr2 * divider_foot_width, + 135, + t, + -90, + t, + -90, + t, + 135, + sqr2 * divider_foot_width, + 135, + full_width, + 135, + ) + self.ctx.restore() + + self.moveTo(-self.burn / sqr2, self.burn * (1 + 1 / sqr2), 45) + self.moveTo(full_width) + + self.polyline( + 0, + 135, + sqr2 * divider_foot_width, + 135, + t, + -90, + t, + -90, + t, + 135, + sqr2 * divider_foot_width, + 135, + ) + + self.move(move_width, move_length, move)
+ + +class MoorBoxSideEdge(edges.BaseEdge): + """ + Edge for the sides of the moor tiles box + """ + + def __init__(self, boxes, corner_length, corner_height, lower_corner): + super().__init__(boxes, None) + self.corner_height = corner_height + self.lower_corner = lower_corner + self.corner_length = corner_length + + def __call__(self, length, **kw): + radius = self.corner_height / 2 + if self.lower_corner: + self.polyline( + length - self.corner_height - self.corner_length, + (90, radius), + 0, + (-90, radius), + self.corner_length, + ) + else: + self.polyline(length) + + def startwidth(self): + return self.corner_height + + def endwidth(self): + return 0 if self.lower_corner else self.corner_height + + +class MoorBoxHoleEdge(edges.BaseEdge): + """ + Edge which does the notches for the moor tiles box + """ + + def __init__(self, boxes, height, corner_height, lower_corner): + super().__init__(boxes, None) + self.height = height + self.corner_height = corner_height + self.lower_corner = lower_corner + + def __call__(self, length, **kw): + one_side_width = (length - self.thickness) / 2 + notch_width = 20 + radius = 6 + upper_edge = (one_side_width - notch_width - 2 * radius) / 2 + hole_start = 10 + lowered_hole_start = 2 + hole_depth = self.height - 2 * radius + lower_edge = notch_width - 2 * radius + + one_side_polyline = lambda margin1, margin2: [ + upper_edge, + (90, radius), + hole_depth - margin1, + (-90, radius), + lower_edge, + (-90, radius), + hole_depth - margin2, + (90, radius), + upper_edge, + ] + + normal_side_polyline = one_side_polyline(hole_start, hole_start) + corner_side_polyline = one_side_polyline( + lowered_hole_start, lowered_hole_start + self.corner_height + ) + + full_polyline = ( + normal_side_polyline + + [0, self.thickness, 0] + + (corner_side_polyline if self.lower_corner else normal_side_polyline) + ) + self.polyline(*full_polyline) + + def startwidth(self): + return self.corner_height + + def endwidth(self): + return 0 if self.lower_corner else self.corner_height + + +class BedHeadEdge(edges.BaseEdge): + """ + Edge which does the head side of the Agricola player box + """ + + def __init__(self, boxes, hole_depth): + super().__init__(boxes, None) + self.hole_depth = hole_depth + + def __call__(self, length, **kw): + hole_length = 16 + upper_corner = 10 + lower_corner = 6 + depth = self.hole_depth - upper_corner - lower_corner + upper_edge = (length - hole_length - 2 * upper_corner) / 2 + lower_edge = hole_length - 2 * lower_corner + + self.polyline( + upper_edge, + (90, upper_corner), + depth, + (-90, lower_corner), + lower_edge, + (-90, lower_corner), + depth, + (90, upper_corner), + upper_edge, + ) + + +class Bed2SidesEdge(edges.BaseEdge): + """ + Edge which does a bed like shape, skipping the next corner. + The next edge should be a NoopEdge + """ + + def __init__(self, boxes, bed_length, full_head_length, full_foot_height): + super().__init__(boxes, None) + self.bed_length = bed_length + self.full_head_length = full_head_length + self.full_foot_height = full_foot_height + + def __call__(self, bed_height, **kw): + foot_corner = 6 + middle_corner = 3 + head_corner = 10 + foot_height = self.full_foot_height - self.thickness - foot_corner + head_length = self.full_head_length - head_corner - self.thickness + corners = foot_corner + middle_corner + head_corner + head_height = bed_height - foot_height - corners + middle_length = self.bed_length - head_length - corners + + self.polyline( + foot_height, + (90, foot_corner), + middle_length, + (-90, middle_corner), + head_height, + (90, head_corner), + head_length, + ) + + +class NoopEdge(edges.BaseEdge): + """ + Edge which does nothing, not even turn or move. + """ + + def __init__(self, boxes): + super().__init__(boxes, None) + + def __call__(self, length, **kw): + # cancel turn + self.corner(-90) +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/alledges.html b/html/_modules/boxes/generators/alledges.html new file mode 100644 index 0000000..40b8f9b --- /dev/null +++ b/html/_modules/boxes/generators/alledges.html @@ -0,0 +1,151 @@ + + + + + + + + boxes.generators.alledges — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.alledges

+#!/usr/bin/env python3
+# Copyright (C) 2013-2018 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class AllEdges(Boxes): + """Showing all edge types""" + + ui_group = "Misc" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings) + self.addSettingsArgs(edges.StackableSettings) + self.addSettingsArgs(edges.HingeSettings) + self.addSettingsArgs(edges.LidSettings) + self.addSettingsArgs(edges.ClickSettings) + self.addSettingsArgs(edges.FlexSettings) + self.addSettingsArgs(edges.HandleEdgeSettings) + + self.buildArgParser(x=100) + + def render(self): + x = self.x + t = self.thickness + + chars = list(self.edges.keys()) + chars.sort(key=lambda c: c.lower() + (c if c.isupper() else '')) + chars.reverse() + + self.moveTo(0, 10*t) + + for c in chars: + with self.saved_context(): + self.move(0, 0, "", True) + self.moveTo(x, 0, 90) + self.edge(t+self.edges[c].startwidth()) + self.corner(90) + self.edges[c](x, h=4*t) + self.corner(90) + self.edge(t+self.edges[c].endwidth()) + self.move(0, 0, "") + + self.moveTo(0, 3*t + self.edges[c].spacing()) + self.text("%s - %s" % (c, self.edges[c].description)) + self.moveTo(0, 12*t)
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/angledbox.html b/html/_modules/boxes/generators/angledbox.html new file mode 100644 index 0000000..fcf5a7f --- /dev/null +++ b/html/_modules/boxes/generators/angledbox.html @@ -0,0 +1,237 @@ + + + + + + + + boxes.generators.angledbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.angledbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+import math
+import copy
+
+
[docs]class AngledBox(Boxes): + """Box with both ends cornered""" + + ui_group = "Box" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.buildArgParser("x", "y", "h", "outside", "bottom_edge") + self.argparser.add_argument( + "--n", action="store", type=int, default=5, + help="number of walls at one side (1+)") + self.argparser.add_argument( + "--top", action="store", type=str, default="none", + choices=["none", "angled hole", "angled lid", "angled lid2"], + help="style of the top and lid") + + def floor(self, x, y, n, edge='e', hole=None, move=None, callback=None, label=""): + r, h, side = self.regularPolygon(2*n+2, h=y/2.0) + t = self.thickness + + if n % 2: + lx = x - 2 * h + side + else: + lx = x - 2 * r + side + + edge = self.edges.get(edge, edge) + + tx = x + 2 * edge.spacing() + ty = y + 2 * edge.spacing() + + if self.move(tx, ty, move, before=True): + return + + self.moveTo((tx-lx)/2., edge.margin()) + + if hole: + with self.saved_context(): + hr, hh, hside = self.regularPolygon(2*n+2, h=y/2.0-t) + dx = side - hside + hlx = lx - dx + + self.moveTo(dx/2.0, t+edge.spacing()) + for i, l in enumerate(([hlx] + ([hside] * n))* 2): + self.edge(l) + self.corner(360.0/(2*n + 2)) + + for i, l in enumerate(([lx] + ([side] * n))* 2): + self.cc(callback, i, 0, edge.startwidth() + self.burn) + edge(l) + self.edgeCorner(edge, edge, 360.0/(2*n + 2)) + + self.move(tx, ty, move, label=label) + + def render(self): + + x, y, h, n = self.x, self.y, self.h, self.n + b = self.bottom_edge + + if n < 1: + n = self.n = 1 + + if x < y: + x, y = y, x + + if self.outside: + x = self.adjustSize(x) + y = self.adjustSize(y) + if self.top == "none": + h = self.adjustSize(h, False) + elif "lid" in self.top and self.top != "angled lid": + h = self.adjustSize(h) - self.thickness + else: + h = self.adjustSize(h) + + t = self.thickness + + r, hp, side = self.regularPolygon(2*n+2, h=y/2.0) + + if n % 2: + lx = x - 2 * hp + side + else: + lx = x - 2 * r + side + + fingerJointSettings = copy.deepcopy(self.edges["f"].settings) + fingerJointSettings.setValues(self.thickness, angle=360./(2 * (n+1))) + fingerJointSettings.edgeObjects(self, chars="gGH") + + with self.saved_context(): + if b != "e": + self.floor(x, y , n, edge='f', move="right", label="Bottom") + if self.top == "angled lid": + self.floor(x, y, n, edge='e', move="right", label="Lower Lid") + self.floor(x, y, n, edge='E', move="right", label="Upper Lid") + elif self.top in ("angled hole", "angled lid2"): + self.floor(x, y, n, edge='F', move="right", hole=True, label="Top Rim and Lid") + if self.top == "angled lid2": + self.floor(x, y, n, edge='E', move="right", label="Upper Lid") + self.floor(x, y , n, edge='F', move="up only") + + fingers = self.top in ("angled lid2", "angled hole") + + cnt = 0 + for j in range(2): + cnt += 1 + if j == 0 or n % 2: + self.rectangularWall(lx, h, move="right", + edges=b+"GfG" if fingers else b+"GeG", + label="wall {}".format(cnt)) + else: + self.rectangularWall(lx, h, move="right", + edges=b+"gfg" if fingers else b+"geg", + label="wall {}".format(cnt)) + for i in range(n): + cnt += 1 + if (i+j*((n+1)%2)) % 2: # reverse for second half if even n + self.rectangularWall(side, h, move="right", + edges=b+"GfG" if fingers else b+"GeG", + label="wall {}".format(cnt)) + else: + self.rectangularWall(side, h, move="right", + edges=b+"gfg" if fingers else b+"geg", + label="wall {}".format(cnt))
+ + + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/angledcutjig.html b/html/_modules/boxes/generators/angledcutjig.html new file mode 100644 index 0000000..fd7d20c --- /dev/null +++ b/html/_modules/boxes/generators/angledcutjig.html @@ -0,0 +1,155 @@ + + + + + + + + boxes.generators.angledcutjig — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.angledcutjig

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class AngledCutJig(Boxes): # Change class name! + """Jig for making angled cuts in a laser cutter""" + + ui_group = "Misc" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings, surroundingspaces=1.) + + # remove cli params you do not need + self.buildArgParser(x=50, y=100) + # Add non default cli params if needed (see argparse std lib) + self.argparser.add_argument( + "--angle", action="store", type=float, default=45., + help="Angle of the cut") + + def bottomCB(self): + t = self.thickness + self.fingerHolesAt(10-t, 4.5*t, 20, 0) + self.fingerHolesAt(30+t, 4.5*t, self.x, 0) + self.fingerHolesAt(10-t, self.y-4.5*t, 20, 0) + self.fingerHolesAt(30+t, self.y-4.5*t, self.x, 0) + + def render(self): + # adjust to the variables you want in the local scope + x, y = self.x, self.y + t = self.thickness + + th = x * math.tan(math.radians(90-self.angle)) + l = (x**2 + th**2)**0.5 + th2 = 20 * math.tan(math.radians(self.angle)) + l2 = (20**2 + th2**2)**0.5 + + self.rectangularWall(30+x+2*t, y, callback=[self.bottomCB], move="right") + self.rectangularWall(l, y, callback=[ + lambda:self.fingerHolesAt(0, 4.5*t, l, 0), None, + lambda:self.fingerHolesAt(0, 4.5*t, l, 0), None], + move="right") + self.rectangularWall(l2, y, callback=[ + lambda:self.fingerHolesAt(0, 4.5*t, l2, 0), None, + lambda:self.fingerHolesAt(0, 4.5*t, l2, 0), None], + move="right") + + self.rectangularTriangle(x, th, "fef", num=2, move="up") + self.rectangularTriangle(20, th2, "fef", num=2, move="up")
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/arcade.html b/html/_modules/boxes/generators/arcade.html new file mode 100644 index 0000000..b963432 --- /dev/null +++ b/html/_modules/boxes/generators/arcade.html @@ -0,0 +1,208 @@ + + + + + + + + boxes.generators.arcade — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.arcade

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class Arcade(Boxes): + """Desktop Arcade Machine""" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.argparser.add_argument( + "--width", action="store", type=float, default=450.0, + help="inner width of the console") + self.argparser.add_argument( + "--monitor_height", action="store", type=float, default=350.0, + help="inner width of the console") + self.argparser.add_argument( + "--keyboard_depth", action="store", type=float, default=150.0, + help="inner width of the console") + + def side(self, move=None): + # TODO: Add callbacks + + y, h = self.y, self.h + t = self.thickness + r = 10 + d_30 = 2* r * math.tan(math.radians(15)) + + tw, th = y+2*r+(self.front+t) * math.sin(math.radians(15)), h+2*r+(self.topback+t)/2**0.5 + if self.move(tw, th, move, True): + return + + self.moveTo(r+(self.front+t) * math.sin(math.radians(15)), 0) + + with self.saved_context(): + self.moveTo(0, r) + self.polyline(y, 90, h, 45, self.topback+t, 90, self.top+2*t, 90, 100, -90, self.monitor_height, -30, self.keyboard_depth+2*t, 90, self.front+t, 75) + + self.fingerHolesAt(10, r+t/2, self.bottom, 0) + self.polyline(y, (90, r)) + self.fingerHolesAt(0.5*t, r+t/2, self.back, 0) + self.fingerHolesAt(h-40-40, r+t/2, self.back, 0) + + self.polyline(h, (45, r)) + self.fingerHolesAt(0, r+t/2, self.topback, 0) + self.fingerHolesAt(self.topback+t/2, r+t, self.top, 90) + self.fingerHolesAt(self.topback, self.top+r+1.5*t, self.speaker, -180) + self.polyline(self.topback+t, (90, r), self.top+2*t, (90, r), 100-2*r, (-90, r), self.monitor_height-2*r-d_30, (-30, r)) + self.fingerHolesAt(-d_30+t, r+.5*t, self.keyboard_depth, 0) + self.fingerHolesAt(-d_30+0.5*t, r+t, self.keyback, 90) + self.fingerHolesAt(self.keyboard_depth-d_30+1.5*t, r+t, self.front, 90) + self.polyline(self.keyboard_depth-d_30+2*t, (90, r), self.front+t, (75, r)) + + self.move(tw, th, move) + + def keyboard(self): + # Add holes for the joystick and buttons here + pass + + def speakers(self): + self.hole(self.width/4., 50, 40) + self.hole(self.width*3/4., 50, 40) + + def render(self): + width = self.width + t = self.thickness + + self.back = 40 + self.front = 120 + self.keyback = 50 + self.speaker = 150 + self.top = 100 + self.topback = 200 + y, h = self.y, self.h = 540, 450 + y = self.y = ((self.topback+self.top+3*t-100+self.monitor_height) / 2**0.5 + + (self.keyboard_depth+2*t)*math.cos(math.radians(15)) + - (self.front+t) * math.sin(math.radians(15))) + h = self.h = ((self.monitor_height-self.topback+self.top+1*t+100) / 2**0.5 + + + (self.keyboard_depth+2*t)*math.sin(math.radians(15)) + + (self.front+t) * math.cos(math.radians(15))) + + self.bottom = y-40-0.5*t + self.backwall = h-40 + + # Floor + self.rectangularWall(width, self.bottom, "efff", move="up") + # Back + self.rectangularWall(width, self.back, "Ffef", move="up") + self.rectangularWall(width, self.backwall, move="up") + self.rectangularWall(width, self.back, "efef", move="up") + + # Front bottom + self.rectangularWall(width, self.front, "efff", move="up") + self.rectangularWall(width, self.keyboard_depth, "FfFf", callback=[self.keyboard], move="up") + self.rectangularWall(width, self.keyback, "ffef", move="up") + # Top + self.rectangularWall(width, self.speaker, "efff", callback=[None, None, self.speakers], move="up") + self.rectangularWall(width, self.top, "FfFf", move="up") + self.rectangularWall(width, self.topback, "ffef", move="up") + # Sides + self.side(move="up") + self.side(move="up")
+ + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/atreus21.html b/html/_modules/boxes/generators/atreus21.html new file mode 100644 index 0000000..0926977 --- /dev/null +++ b/html/_modules/boxes/generators/atreus21.html @@ -0,0 +1,209 @@ + + + + + + + + boxes.generators.atreus21 — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.atreus21

+"""Generator for a split atreus keyboard."""
+
+from copy import deepcopy
+
+from boxes import Boxes, Color, holeCol, restore, boolarg
+from boxes.edges import FingerJointSettings
+from .keyboard import Keyboard
+
+
+
[docs]class Atreus21(Boxes, Keyboard): + """Generator for a split atreus keyboard.""" + ui_group = 'Misc' + btn_size = 15.6 + half_btn = btn_size / 2 + border = 6 + + def __init__(self): + super().__init__() + self.add_common_keyboard_parameters( + # By default, columns from Atreus 21 + default_columns_definition='4@3/4@6/4@11/4@5/4@0/1@{}'.format(self.btn_size * 0.5) + ) + + def render(self): + """Renders the keyboard.""" + + self.moveTo(10, 30) + case_x, case_y = self._case_x_y() + + margin = 2 * self.border + 1 + + for reverse in [False, True]: + # keyholder + self.outer() + self.half(reverse=reverse) + self.holes() + self.moveTo(case_x + margin) + + # support + self.outer() + self.half(self.support, reverse=reverse) + self.holes() + self.moveTo(-case_x - margin, case_y + margin) + + # hotplug + self.outer() + self.half(self.hotplug, reverse=reverse) + self.holes() + self.moveTo(case_x + margin) + + # border + self.outer() + self.rim() + self.holes() + self.moveTo(-case_x - margin, case_y + margin) + + def holes(self, diameter=3, margin=1.5): + case_x, case_y = self._case_x_y() + for x in [-margin, case_x + margin]: + for y in [-margin, case_y + margin]: + self.hole(x, y, d=diameter) + + def micro(self): + x = 17.9 + y = 33 + b = self.border + case_x, case_y = self._case_x_y() + self.rectangularHole( + x * -.5 + case_x + b * .5, + y * -.5 + case_y + b * .5, + x, y + ) + + @restore + def rim(self): + x, y = self._case_x_y() + self.moveTo(x * .5, y * .5) + self.rectangularHole(0, 0, x, y, 5) + + @restore + def outer(self): + x, y = self._case_x_y() + b = self.border + self.moveTo(0, -b) + corner = [90, b] + self.polyline(*([x, corner, y, corner] * 2)) + + @restore + def half(self, hole_cb=None, reverse=False): + if hole_cb == None: + hole_cb = self.key + self.moveTo(self.half_btn, self.half_btn) + self.apply_callback_on_columns( + hole_cb, + self.columns_definition, + reverse=reverse, + ) + + def support(self): + self.configured_plate_cutout(support=True) + + def hotplug(self): + self.pcb_holes( + with_hotswap=self.hotswap_enable, + with_pcb_mount=self.pcb_mount_enable, + with_diode=self.diode_enable, + with_led=self.led_enable, + ) + + def key(self): + self.configured_plate_cutout() + + # get case sizes + def _case_x_y(self): + spacing = Keyboard.STANDARD_KEY_SPACING + margin = spacing - self.btn_size + x = len(self.columns_definition) * spacing - margin + y = max(offset + keys * spacing for (offset, keys) in self.columns_definition) - margin + return x, y
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/basedbox.html b/html/_modules/boxes/generators/basedbox.html new file mode 100644 index 0000000..8d9c296 --- /dev/null +++ b/html/_modules/boxes/generators/basedbox.html @@ -0,0 +1,148 @@ + + + + + + + + boxes.generators.basedbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.basedbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
+
[docs]class BasedBox(Boxes): + """Fully closed box on a base""" + + ui_group = "Box" + + description = """This box is more of a building block than a finished item. +Use a vector graphics program (like Inkscape) to add holes or adjust the base +plate. The width of the "brim" can also be adjusted with the **edge_width** + parameter in the **Finger Joints Settings**. + +See ClosedBox for variant without a base. +""" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.buildArgParser("x", "y", "h", "outside") + + def render(self): + + x, y, h = self.x, self.y, self.h + + if self.outside: + x = self.adjustSize(x) + y = self.adjustSize(y) + h = self.adjustSize(h) + + t = self.thickness + + self.rectangularWall(x, h, "fFFF", move="right", label="Wall 1") + self.rectangularWall(y, h, "ffFf", move="up", label="Wall 2") + self.rectangularWall(y, h, "ffFf", label="Wall 4") + self.rectangularWall(x, h, "fFFF", move="left up", label="Wall 3") + + self.rectangularWall(x, y, "ffff", move="right", label="Top") + self.rectangularWall(x, y, "hhhh", label="Base")
+ + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/bayonetbox.html b/html/_modules/boxes/generators/bayonetbox.html new file mode 100644 index 0000000..2f5582a --- /dev/null +++ b/html/_modules/boxes/generators/bayonetbox.html @@ -0,0 +1,218 @@ + + + + + + + + boxes.generators.bayonetbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.bayonetbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2019 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class BayonetBox(Boxes): + """Round box made from layers with twist on top""" + + description = """Glue together - all outside rings to the bottom, all inside rings to the top.""" + ui_group = "Box" + + def __init__(self): + Boxes.__init__(self) + + self.argparser.add_argument( + "--diameter", action="store", type=float, default=50., + help="Diameter of the box in mm") + self.argparser.add_argument( + "--lugs", action="store", type=int, default=10, + help="number of locking lugs") + self.argparser.add_argument( + "--alignment_pins", action="store", type=float, default=1.0, + help="diameter of the alignment pins") + self.buildArgParser("outside") + + def alignmentHoles(self, inner=False, outer=False): + d = self.diameter + r = d / 2 + t = self.thickness + p = 0.05*t + l = self.lugs + + a = 180 / l + with self.saved_context(): + for i in range(3): + if outer: + self.hole(r-t/2, 0, d=self.alignment_pins) + if inner: + self.hole(r-2*t-p, 0, d=self.alignment_pins) + self.moveTo(0, 0, 360/3) + + def lowerLayer(self, asPart=False, move=None): + d = self.diameter + r = d / 2 + t = self.thickness + p = 0.05*t + l = self.lugs + + a = 180 / l + + if asPart: + if self.move(d, d, move, True): + return + self.moveTo(d/2, d/2) + + self.alignmentHoles(inner=True) + self.hole(0, 0, r=d/2 - 2.5*t) + self.moveTo(d/2 - 1.5*t, 0, -90) + + for i in range(l): + self.polyline(0, (-4/3*a, r-1.5*t), 0, 90, 0.5*t, -90, 0, (-2/3*a, r-t), 0, -90, 0.5*t, 90) + + if asPart: + self.move(d, d, move) + + def lowerCB(self): + d = self.diameter + r = d / 2 + t = self.thickness + p = 0.05*t + l = self.lugs + + a = 180 / l + + self.alignmentHoles(outer=True) + with self.saved_context(): + self.lowerLayer() + + self.moveTo(d/2 - 1.5*t+p, 0, -90) + for i in range(l): + self.polyline(0, (-2/3*a, r-1.5*t+p), 0, 90, 0.5*t, -90, 0, (-4/3*a, r-t+p), 0, -90, 0.5*t, 90) + + + def upperCB(self): + d = self.diameter + r = d / 2 + t = self.thickness + p = 0.05*t + l = self.lugs + + a = 180 / l + + self.hole(0, 0, r=d/2 - 2.5*t) + self.hole(0, 0, r=d/2 - 1.5*t) + self.alignmentHoles(inner=True, outer=True) + self.moveTo(d/2 - 1.5*t, 0, -90) + + for i in range(l): + self.polyline(0, (-1.3*a, r-1.5*t+p), 0, 90, 0.5*t, -90, 0, (-0.7*a, r-t+p), 0, -90, 0.5*t, 90) + + + def render(self): + d = self.diameter + t = self.thickness + p = 0.05*t + + if not self.outside: + self.diameter = d = d - 3*t + + + self.parts.disc(d, callback=lambda: self.alignmentHoles(outer=True), move="right") + self.parts.disc(d, callback=lambda: (self.alignmentHoles(outer=True), self.hole(0, 0, d/2-1.5*t)), move="right") + self.parts.disc(d, callback=self.lowerCB, move="right") + self.parts.disc(d, callback=self.upperCB, move="right") + self.parts.disc(d, callback=lambda : self.alignmentHoles(inner=True),move="right")
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/bintray.html b/html/_modules/boxes/generators/bintray.html new file mode 100644 index 0000000..6e2af97 --- /dev/null +++ b/html/_modules/boxes/generators/bintray.html @@ -0,0 +1,249 @@ + + + + + + + + boxes.generators.bintray — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.bintray

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+import math, copy
+
+
[docs]class BinFrontEdge(edges.BaseEdge): + char = "B" + def __call__(self, length, **kw): + f = self.settings.front + a1 = math.degrees(math.atan(f/(1-f))) + a2 = 45 + a1 + self.corner(-a1) + for i, l in enumerate(self.settings.sy): + self.edges["e"](l* (f**2+(1-f)**2)**0.5) + self.corner(a2) + self.edges["f"](l*f*2**0.5) + if i < len(self.settings.sy)-1: + if self.char == "B": + self.polyline(0, 45, 0.5*self.settings.hi, + -90, self.thickness, -90, 0.5*self.settings.hi, 90-a1) + else: + self.polyline(0, -45, self.thickness, -a1) + else: + self.corner(-45) + + def margin(self): + return max(self.settings.sy) * self.settings.front
+ +class BinFrontSideEdge(BinFrontEdge): + char = 'b' + +
[docs]class BinTray(Boxes): + """A Type tray variant to be used up right with sloped walls in front""" + + ui_group = "Shelf" + + def __init__(self): + Boxes.__init__(self) + self.buildArgParser("sx", "sy", "h", "outside", "hole_dD") + self.addSettingsArgs(edges.FingerJointSettings, surroundingspaces=0.5) + self.argparser.add_argument( + "--front", action="store", type=float, default=0.4, + help="fraction of bin height covert with slope") + + def xSlots(self): + posx = -0.5 * self.thickness + for x in self.sx[:-1]: + posx += x + self.thickness + posy = 0 + for y in self.sy: + self.fingerHolesAt(posx, posy, y) + posy += y + self.thickness + + def ySlots(self): + posy = -0.5 * self.thickness + for y in self.sy[:-1]: + posy += y + self.thickness + posx = 0 + for x in self.sx: + self.fingerHolesAt(posy, posx, x) + posx += x + self.thickness + + def addMount(self): + ds = self.hole_dD[0] + + if len(self.hole_dD) < 2: # if no head diameter is given + dh = 0 # only a round hole is generated + y = max (self.thickness * 1.25, self.thickness * 1.0 + ds) # and we assume that a typical screw head diameter is twice the shaft diameter + else: + dh = self.hole_dD[1] # use given head diameter + y = max (self.thickness * 1.25, self.thickness * 1.0 + dh / 2) # and offset the hole to have enough space for the head + + dx = sum(self.sx) + self.thickness * (len(self.sx) - 1) + x1 = dx * 0.125 + x2 = dx * 0.875 + + self.mountingHole(x1, y, ds, dh, -90) + self.mountingHole(x2, y, ds, dh, -90) + + def xHoles(self): + posx = -0.5 * self.thickness + for x in self.sx[:-1]: + posx += x + self.thickness + self.fingerHolesAt(posx, 0, self.hi) + + def frontHoles(self, i): + def CB(): + posx = -0.5 * self.thickness + for x in self.sx[:-1]: + posx += x + self.thickness + self.fingerHolesAt(posx, 0, self.sy[i]*self.front*2**0.5) + return CB + + def yHoles(self): + posy = -0.5 * self.thickness + for y in reversed(self.sy[1:]): + posy += y + self.thickness + self.fingerHolesAt(posy, 0, self.hi) + + def render(self): + if self.outside: + self.sx = self.adjustSize(self.sx) + self.sy = self.adjustSize(self.sy) + self.h = self.adjustSize(self.h, e2=False) + + x = sum(self.sx) + self.thickness * (len(self.sx) - 1) + y = sum(self.sy) + self.thickness * (len(self.sy) - 1) + + h = self.h + hi = self.hi = h + t = self.thickness + self.front = min(self.front, 0.999) + + self.addPart(BinFrontEdge(self, self)) + self.addPart(BinFrontSideEdge(self, self)) + + angledsettings = copy.deepcopy(self.edges["f"].settings) + angledsettings.setValues(self.thickness, True, angle=45) + angledsettings.edgeObjects(self, chars="gGH") + + # outer walls + e = ["F", "f", edges.SlottedEdge(self, self.sx[::-1], "G"), "f"] + + self.rectangularWall(x, h, e, callback=[self.xHoles], move="right", label="bottom") + self.rectangularWall(y, h, "FFbF", callback=[self.yHoles, ], move="up", label="left") + self.rectangularWall(y, h, "FFbF", callback=[self.yHoles, ], label="right") + self.rectangularWall(x, h, "Ffef", callback=[self.xHoles, ], move="left", label="top") + self.rectangularWall(y, h, "FFBF", move="up only") + + # floor + self.rectangularWall(x, y, "ffff", callback=[self.xSlots, self.ySlots, self.addMount], move="right", label="back") + # Inner walls + for i in range(len(self.sx) - 1): + e = [edges.SlottedEdge(self, self.sy, "f"), "f", "B", "f"] + self.rectangularWall(y, hi, e, move="up", label="inner vertical " + str(i+1)) + + for i in range(len(self.sy) - 1): + e = [edges.SlottedEdge(self, self.sx, "f", slots=0.5 * hi), "f", + edges.SlottedEdge(self, self.sx[::-1], "G"), "f"] + self.rectangularWall(x, hi, e, move="up", label="inner horizontal " + str(i+1)) + + # Front walls + for i in range(len(self.sy)): + e = [edges.SlottedEdge(self, self.sx, "g"), "F", "e", "F"] + self.rectangularWall(x, self.sy[i]*self.front*2**0.5, e, callback=[self.frontHoles(i)], move="up", label="retainer " + str(i+1))
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/birdhouse.html b/html/_modules/boxes/generators/birdhouse.html new file mode 100644 index 0000000..c378a5a --- /dev/null +++ b/html/_modules/boxes/generators/birdhouse.html @@ -0,0 +1,165 @@ + + + + + + + + boxes.generators.birdhouse — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.birdhouse

+#!/usr/bin/env python3
+# Copyright (C) 2013-2022 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class BirdHouse(Boxes): + """Simple Bird House""" + + ui_group = "Unstable" # "Misc" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings, finger=10.0,space=10.0) + + # remove cli params you do not need + self.buildArgParser(x=200, y=200, h=200) + + def side(self, x, h, edges="hfeffef", callback=None, move=None): + angles = (90, 0, 45, 90, 45, 0, 90) + roof = 2**0.5 * x / 2 + t = self.thickness + lengths = (x, h, t, roof, roof, t, h) + + edges = [self.edges.get(e, e) for e in edges] + edges.append(edges[0]) # wrap arround + + tw = x + edges[1].spacing() + edges[-2].spacing() + th = h + x/2 + t + edges[0].spacing() + max(edges[3].spacing(), edges[4].spacing()) + + if self.move(tw, th, move, True): + return + + self.moveTo(edges[-2].spacing()) + for i in range(7): + self.cc(callback, i, y=self.burn+edges[i].startwidth()) + edges[i](lengths[i]) + self.edgeCorner(edges[i], edges[i+1], angles[i]) + + self.move(tw, th, move) + + def side_hole(self, width): + self.rectangularHole(width/2, self.h/2, + 0.75*width, 0.75*self.h, + r=self.thickness) + + def render(self): + x, y, h = self.x, self.y, self.h + + roof = 2**0.5 * x / 2 + + cbx = [lambda: self.side_hole(x)] + cby = [lambda: self.side_hole(y)] + + self.side(x, h, callback=cbx, move="right") + self.side(x, h, callback=cbx, move="right") + self.rectangularWall(y, h, "hFeF", callback=cby, move="right") + self.rectangularWall(y, h, "hFeF", callback=cby, move="right") + self.rectangularWall(x, y, "ffff", move="right") + self.edges["h"].settings.setValues(self.thickness, relative=False, edge_width=0.1*roof) + self.flangedWall(y, roof, "ehfh", r=0.2*roof, flanges=[0.2*roof], move="right") + self.flangedWall(y, roof, "ehFh", r=0.2*roof, flanges=[0.2*roof], move="right")
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/bottlestack.html b/html/_modules/boxes/generators/bottlestack.html new file mode 100644 index 0000000..eef1a66 --- /dev/null +++ b/html/_modules/boxes/generators/bottlestack.html @@ -0,0 +1,255 @@ + + + + + + + + boxes.generators.bottlestack — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.bottlestack

+#!/usr/bin/env python3
+# Copyright (C) 2013-2020 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class BottleStack(Boxes): + """Stack bottles in a fridge""" + + description = """When rendered with the "double" option the parts with the double slots get connected the shorter beams in the asymetrical slots. + +Without the "double" option the stand is a bit more narrow. +""" + + ui_group = "Misc" + + def __init__(self): + Boxes.__init__(self) + + self.argparser.add_argument( + "--diameter", action="store", type=float, default=80, + help="diameter of the bottles in mm") + self.argparser.add_argument( + "--number", action="store", type=int, default=3, + help="number of bottles to hold in the bottom row") + self.argparser.add_argument( + "--depth", action="store", type=float, default=80, + help="depth of the stand along the base of the bottles") + self.argparser.add_argument( + "--double", action="store", type=boolarg, default=True, + help="two pieces that can be combined to up to double the width") + + + + def front(self, h_sides, offset=0, move=None): + t = self.thickness + a = 60 + nr = self.number + r1 = self.diameter / 2.0 # bottle + r2 = r1 / math.cos(math.radians(90-a)) - r1 # inbetween + if self.double: + r3 = 1.5*t # upper corners + else: + r3 = .5*t + h = (r1+r2) * (1-math.cos(math.radians(a))) + h_extra = 1*t + h_s = h_sides - t + p = 0.05*t # play + + tw, th = nr * r1 * 2 + 2*r3, h + 2*t + + + if self.move(tw, th, move, True): + return + + open_sides = r3 <= 0.5*t + + if offset == 0: + slot = [0, 90, h_s, -90, t, -90, h_s, 90] + if open_sides: + self.moveTo(0, h_s) + self.polyline(r3-0.5*t) + self.polyline(*slot[4:]) + else: + self.polyline(r3-0.5*t) + self.polyline(*slot) + for i in range(nr-open_sides): + self.polyline(2*r1-t) + self.polyline(*slot) + if open_sides: + self.polyline(2*r1-t) + self.polyline(*slot[:-3]) + self.polyline(r3-0.5*t) + else: + self.polyline(r3-0.5*t) + else: + slot = [0, 90, h_s, -90, t, -90, h_s, 90] + h_s += t + slot2 = [0, 90, h_s, -90, t+2*p, -90, h_s, 90] + if open_sides: + self.moveTo(0, h_s) + self.polyline(t+p, -90, h_s, 90) + else: + self.polyline(r3-0.5*t-p) + self.polyline(*slot2) + self.polyline(t-p) + self.polyline(*slot) + self.polyline(2*r1-5*t) + self.polyline(*slot) + self.polyline(t-p) + self.polyline(*slot2) + for i in range(1, nr-open_sides): + self.polyline(2*r1-3*t-p) + self.polyline(*slot) + self.polyline(t-p) + self.polyline(*slot2) + if open_sides: + self.polyline(2*r1-3*t-p) + self.polyline(*slot) + self.polyline(t-p) + self.polyline(0, 90, h_s, -90, t+p) + else: + self.polyline(r3-0.5*t-p) + + if open_sides: + h_extra -= h_s + + self.polyline(0, 90, h_extra+h-r3, (90, r3)) + + for i in range(nr): + self.polyline(0, (a, r2), 0, (-2*a, r1), 0, (a, r2)) + self.polyline(0, (90, r3), h_extra+h-r3, 90) + + self.move(tw, th, move) + + def side(self, l, h, short=False, move=None): + t = self.thickness + short = bool(short) + + tw, th = l + 2*t - 4*t*short, h + + if self.move(tw, th, move, True): + return + + self.moveTo(t, 0) + + self.polyline(l-3*t*short) + if short: + end = [90, h-t, 90, t, -90, t, 90] + else: + end = [(90, t), h-2*t, (90, t), 0, 90, t, -90, t, -90, t, 90] + self.polyline(0, *end) + self.polyline(l-2*t- 3*t*short) + self.polyline(0, *reversed(end)) + + self.move(tw, th, move) + + def render(self): + t = self.thickness + d = self.depth + nr = self.number + h_sides = 2*t + pieces = 2 if self.double else 1 + + for offset in range(pieces): + self.front(h_sides, offset, move="up") + self.front(h_sides, offset, move="up") + + for short in range(pieces): + for i in range(nr+1): + self.side(d, h_sides, short, move="up")
+ + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/bottletag.html b/html/_modules/boxes/generators/bottletag.html new file mode 100644 index 0000000..9e34e54 --- /dev/null +++ b/html/_modules/boxes/generators/bottletag.html @@ -0,0 +1,179 @@ + + + + + + + + boxes.generators.bottletag — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.bottletag

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
+
[docs]class BottleTag(Boxes): + """Paper slip over bottle tag""" + + ui_group = "Misc" # see ./__init__.py for names + + def __init__(self): + Boxes.__init__(self) + + self.buildArgParser() + # Add non default cli params if needed (see argparse std lib) + self.argparser.add_argument( + "--width", action="store", type=float, default=72, + help="width of neck tag") + self.argparser.add_argument( + "--height", action="store", type=float, default=98, + help="height of neck tag") + self.argparser.add_argument( + "--min_diameter", action="store", type=float, default=24, + help="inner diameter of bottle neck hole") + self.argparser.add_argument( + "--max_diameter", action="store", type=float, default=50, + help="outer diameter of bottle neck hole") + self.argparser.add_argument( + "--radius", action="store", type=float, default=15, + help="corner radius of bottom tag") + self.argparser.add_argument( + "--segment_width", action="store", type=int, default=3, + help="inner segment width") + + def render(self): + # adjust to the variables you want in the local scope + width = self.width + height = self.height + r_min = self.min_diameter / 2 + r_max = self.max_diameter / 2 + r = self.radius + segment_width = self.segment_width + + # tag outline + self.moveTo(r) + self.edge(width - r - r) + self.corner(90, r) + self.edge(height - width / 2.0 - r) + self.corner(180, width / 2) + self.edge(height - width / 2.0 - r) + self.corner(90, r) + + # move to centre of hole and cut the inner circle + self.moveTo(width / 2 - r, height - width / 2) + with self.saved_context(): + self.moveTo(0, -r_min) + self.corner(360, r_min) + + # draw the radial lines approx 2mm apart on r_min + seg_angle = math.degrees(segment_width / r_min) + # for neatness, we want an integral number of cuts + num = math.floor(360 / seg_angle) + for i in range(num): + with self.saved_context(): + self.moveTo(0, 0, i * 360.0 / num) + self.moveTo(r_min) + self.edge(r_max - r_min) + # Add some right angle components to reduce tearing + with self.saved_context(): + self.moveTo(0, 0, 90) + self.edge(0.5) + with self.saved_context(): + self.moveTo(0, 0, -90) + self.edge(0.5)
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/breadbox.html b/html/_modules/boxes/generators/breadbox.html new file mode 100644 index 0000000..cb10b7a --- /dev/null +++ b/html/_modules/boxes/generators/breadbox.html @@ -0,0 +1,240 @@ + + + + + + + + boxes.generators.breadbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.breadbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2022 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class BreadBox(Boxes): + """A BreadBox with a gliding door""" + + ui_group = "Unstable" # "FlexBox" + + description = """Beware of the rolling shutter effect! Use wax on sliding surfaces. +""" + + def side(self, l, h, r, move=None): + t = self.thickness + + if self.move(l+2*t, h+2*t, move, True): + return + + self.moveTo(t, t) + + self.ctx.save() + + n = self.n + a = 90. / n + ls = 2*math.sin(math.radians(a/2)) * (r-2.5*t) + + self.fingerHolesAt(2*t, 0, h-r, 90) + self.moveTo(2.5*t, h-r, 90-a/2) + for i in range(n): + self.fingerHolesAt(0, 0.5*t, ls, 0) + self.moveTo(ls, 0, -a) + self.moveTo(0, 0, a/2) + self.fingerHolesAt(0, 0.5*t, l / 2 - r, 0) + self.ctx.restore() + + self.edges["f"](l) + self.polyline(t, 90, h-r, (90, r+t), l/2-r, 90, t, -90, 0,) + self.edges["f"](l/2) + self.polyline(0, 90) + self.edges["f"](h) + + self.move(l+2*t, h+2*t, move) + + def cornerRadius(self, r, two=False, move=None): + s = self.spacing + if self.move(r, r+s, move, True): + return + for i in range(2 if two else 1): + self.polyline(r, 90, r, 180, 0, (-90, r), 0 ,-180) + self.moveTo(r, r+s, 180) + self.move(r, r+s, move) + + def rails(self, l, h, r, move=None): + t = self.thickness + s = self.spacing + tw, th = l/2+2.5*t+3*s, h+1.5*t+3*s + + if self.move(tw, th, move, True): + return + + self.moveTo(2.5*t+s, 0) + self.polyline(l/2-r, (90, r+t), h-r, 90, t, 90, h-r, (-90, r), l/2-r, 90, t, 90) + self.moveTo(-t-s, t+s) + self.polyline(l/2-r, (90, r+t), h-r, 90, t, 90, h-r, (-90, r), l/2-r, 90, t, 90) + self.moveTo(+t-s, t+s) + self.polyline(l/2-r, (90, r-1.5*t), h-r, 90, t, 90, h-r, (-90, r-2.5*t), l/2-r, 90, t, 90) + self.moveTo(-t-s, t+s) + self.polyline(l/2-r, (90, r-1.5*t), h-r, 90, t, 90, h-r, (-90, r-2.5*t), l/2-r, 90, t, 90) + + self.move(tw, th, move) + + def door(self, l, h, move=None): + t = self.thickness + if self.move(l, h, move, True): + return + self.fingerHolesAt(t, t, h-2*t) + self.edge(2*t) + self.edges["X"](l-2*t, h) + self.polyline(0, 90, h, 90, l, 90, h, 90) + self.move(l, h, move) + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings, surroundingspaces=0.5) + self.addSettingsArgs(edges.FlexSettings, distance=.75, connection=2.) + + self.buildArgParser(x=150, y=100, h=100) + self.argparser.add_argument( + "--radius", action="store", type=float, default=40.0, + help="radius of the corners") + + def render(self): + x, y, h, r = self.x, self.y, self.h, self.radius + self.n = n = 3 + + if not r: + self.radius = r = h / 2 + self.radius = r = min(r, h/2) + + t = self.thickness + self.ctx.save() + self.side(x, h, r, move="right") + self.side(x, h, r, move="right") + self.rectangularWall(y, h, "fFfF", move="right") + + self.ctx.restore() + self.side(x, h, r, move="up only") + + self.rectangularWall(x, y, "FEFF", move="right") + self.rectangularWall(x/2, y, "FeFF", move="right") + + self.door(x/2 + h - 2*r + 0.5*math.pi*r + 2*t, y-0.2*t, move="right") + + self.rectangularWall(2*t, y-2.2*t, edges="eeef", move="right") + + + a = 90. / n + ls = 2*math.sin(math.radians(a/2)) * (r-2.5*t) + + edges.FingerJointSettings(t, angle=a).edgeObjects(self, chars="aA") + edges.FingerJointSettings(t, angle=a/2).edgeObjects(self, chars="bB") + + + self.rectangularWall(h-r, y, "fbfe", move="right") + + self.rectangularWall(ls, y, "fafB", move="right") + + for i in range(n-2): + self.rectangularWall(ls, y, "fafA", move="right") + + self.rectangularWall(ls, y, "fbfA", move="right") + self.rectangularWall(x/2 - r, y, "fefB", move="right") + + self.rails(x, h, r, move="right mirror") + self.cornerRadius(r, two=True, move="right")
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/burntest.html b/html/_modules/boxes/generators/burntest.html new file mode 100644 index 0000000..ee77565 --- /dev/null +++ b/html/_modules/boxes/generators/burntest.html @@ -0,0 +1,164 @@ + + + + + + + + boxes.generators.burntest — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.burntest

+#!/usr/bin/env python3
+# Copyright (C) 2013-2019 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class BurnTest(Boxes): + """Test different burn values""" + description = """This generator will make shapes that you can use to select +optimal value for burn parameter for other generators. After burning try to +attach sides with the same value and use best fitting one on real projects. +In this generator set burn in the Default Settings to the lowest value +to be tested. To get an idea cut a rectangle with known nominal size and +measure the shrinkage due to the width of the laser cut. Now you can +measure the burn value that you should use in other generators. It is half +the difference of the overall size as shrinkage is occurring on both +sides. You can use the reference rectangle as it is rendered without burn +correction. + +See also LBeam that can serve as compact BurnTest and FlexTest for testing flex settings. +""" + + ui_group = "Part" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings) + self.buildArgParser(x=100) + self.argparser.add_argument( + "--step", action="store", type=float, default=0.01, + help="increases in burn value between the sides") + self.argparser.add_argument( + "--pairs", action="store", type=int, default=2, + help="number of pairs (each testing four burn values)") + + + def render(self): + x, s = self.x, self.step + t = self.thickness + + fsize = 12.5 * self.x / 100 if self.x < 81 else 10 + + self.moveTo(t, t) + + for cnt in range(self.pairs): + + for i in range(4): + self.text("%.3fmm" % self.burn, x/2, t, fontsize = fsize, align="center", color=Color.ETCHING) + self.edges["f"](x) + self.corner(90) + self.burn += s + + self.burn -= 4*s + + self.moveTo(x+2*t+self.spacing, -t) + for i in range(4): + self.text("%.3fmm" % self.burn, x/2, t, fontsize = fsize, align="center", color=Color.ETCHING) + self.edges["F"](x) + self.polyline(t, 90, t) + self.burn += s + self.moveTo(x+2*t+self.spacing, t)
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/can_storage.html b/html/_modules/boxes/generators/can_storage.html new file mode 100644 index 0000000..2000ff4 --- /dev/null +++ b/html/_modules/boxes/generators/can_storage.html @@ -0,0 +1,455 @@ + + + + + + + + boxes.generators.can_storage — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.can_storage

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERself.canHightANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+import math
+
+class FrontEdge(edges.BaseEdge):
+    char = "a"
+
+    def __call__(self, length, **kw):
+        x = math.ceil( ((self.canDiameter * 0.5 + 2 * self.thickness) * math.sin(math.radians(self.chuteAngle))) / self.thickness)
+        if self.top_edge != "e":
+            self.corner(90, self.thickness)
+            self.edge(0.5 * self.canDiameter)            
+            self.corner(-90, 0.25 * self.canDiameter)
+        else:
+            self.moveTo(-self.burn, self.canDiameter + self.thickness, -90)
+            self.corner(90, 0.25 * self.canDiameter)
+            self.edge(self.thickness)
+
+        self.edge(0.5 * self.canDiameter - self.thickness)
+        self.corner(-90, 0.25 * self.canDiameter)
+        self.edge(0.5 * self.canDiameter)
+        self.corner(90, self.thickness)
+        self.edge(x * self.thickness )
+        self.corner(90, self.thickness)
+        self.edge(0.5 * self.canDiameter)
+        self.corner(-90, 0.25 * self.canDiameter)
+        self.edge(0.5 * self.canDiameter - (1 + x) * self.thickness + self.top_chute_height + self.bottom_chute_height - self.barrier_height)
+        self.corner(-90, 0.25 * self.canDiameter)
+        self.edge(0.5 * self.canDiameter)
+        self.corner(90, self.thickness)
+        self.edge(self.barrier_height)
+        self.edge(self.thickness)
+
+class TopChuteEdge(edges.BaseEdge):
+    char = "b"
+
+    def __call__(self, length, **kw):
+        self.edge(0.2 * length - self.thickness)
+        self.corner(90, self.thickness)
+        self.edge(1.5*self.canDiameter - 2 * self.thickness)
+        self.corner(-90, self.thickness)
+        self.edge(0.6 * length - 2 * self.thickness)
+        self.corner(-90, self.thickness)
+        self.edge(1.5*self.canDiameter - 2 * self.thickness)
+        self.corner(90, self.thickness)
+        self.edge(0.2 * length - self.thickness)
+
+class BarrierEdge(edges.BaseEdge):
+    char = "A"
+
+    def __call__(self, length, **kw):
+        self.edge(0.2*length)
+        self.corner(90,self.thickness/2)
+        self.corner(-90,self.thickness/2)
+        self.edge(0.6*length-2*self.thickness)
+        self.corner(-90,self.thickness/2)
+        self.corner(90,self.thickness/2)
+        self.edge(0.2*length)
+
+    def startwidth(self):
+        return self.boxes.thickness
+
+
[docs]class CanStorage(Boxes): + """Storage box for round containers""" + + description = """ +for AA batteries: + +![CanStorage for AA batteries](static/samples/CanStorageAA.jpg) + +for canned tomatos: +""" + + + ui_group = "Misc" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings, finger=2.0, space=2.0, surroundingspaces=0.0) + self.addSettingsArgs(edges.StackableSettings) + self.addSettingsArgs(fillHolesSettings) + + self.argparser.add_argument( + "--top_edge", action="store", + type=ArgparseEdgeType("efhŠ"), choices=list("efhŠ"), + default="Š", help="edge type for top edge") + self.argparser.add_argument( + "--bottom_edge", action="store", + type=ArgparseEdgeType("eEš"), choices=list("eEš"), + default="š", help="edge type for bottom edge") + + # Add non default cli params if needed (see argparse std lib) + self.argparser.add_argument( + "--canDiameter", action="store", type=float, default=75, + help="outer diameter of the cans to be stored (in mm)") + self.argparser.add_argument( + "--canHight", action="store", type=float, default=110, + help="hight of the cans to be stored (in mm)") + self.argparser.add_argument( + "--canNum", action="store", type=int, default=12, + help="number of cans to be stored") + self.argparser.add_argument( + "--chuteAngle", action="store", type=float, default=5.0, + help="slope angle of the chutes") + + def DrawPusher(self, dbg = False): + with self.saved_context(): + if dbg == False: + self.moveTo(0,self.thickness) + self.edge(0.25*self.pusherA) + self.corner(-90) + self.edge(self.thickness) + self.corner(90) + self.edge(0.5*self.pusherA) + self.corner(90) + self.edge(self.thickness) + self.corner(-90) + self.edge(0.25*self.pusherA) + + self.corner(90-self.chuteAngle) + + self.edge(0.25*self.pusherB) + self.corner(-90) + self.edge(self.thickness) + self.corner(90) + self.edge(0.5*self.pusherB) + self.corner(90) + self.edge(self.thickness) + self.corner(-90) + self.edge(0.25*self.pusherB) + + self.corner(90+self.pusherAngle+self.chuteAngle) + self.edge(self.pusherC) + + def cb_top_chute(self, nr): + if nr == 0: + # fill with holes + border = [ + (0, 0), + (self.top_chute_depth, 0), + (self.top_chute_depth, 0.2 * self.width - self.thickness), + (self.top_chute_depth - self.thickness, 0.2 * self.width), + (self.top_chute_depth - 1.5*self.canDiameter, 0.2 * self.width), + (self.top_chute_depth - 1.5*self.canDiameter, 0.8 * self.width), + (self.top_chute_depth - self.thickness, 0.8 * self.width), + (self.top_chute_depth, 0.8 * self.width + self.thickness), + (self.top_chute_depth, self.width), + (0, self.width), + ] + + if self.fillHoles_fill_pattern != "no fill": + self.fillHoles( + pattern="hbar", + border=border, + max_radius = min(2*self.thickness, self.fillHoles_hole_max_radius) if self.fillHoles_fill_pattern in ["hbar", "vbar"] else min(2*self.thickness, self.width/30), + hspace=min(2*self.thickness, self.fillHoles_space_between_holes) if self.fillHoles_fill_pattern in ["hbar", "vbar"] else min(2*self.thickness, self.width/20), + bspace=min(2*self.thickness, self.fillHoles_space_to_border) if self.fillHoles_fill_pattern in ["hbar", "vbar"] else min(2*self.thickness, self.width/20), + bar_length=self.fillHoles_bar_length, + max_random=self.fillHoles_max_random, + ) + + def cb_top(self, nr): + if nr == 0: + # fill with holes + border = [ + (0, 0), + (self.depth, 0), + (self.depth, self.width), + (0, self.width), + ] + + if self.fillHoles_fill_pattern != "no fill": + self.fillHoles( + pattern="hbar", + border=border, + max_radius = min(2*self.thickness, self.fillHoles_hole_max_radius) if self.fillHoles_fill_pattern in ["hbar", "vbar"] else min(2*self.thickness, self.width/30), + hspace=min(2*self.thickness, self.fillHoles_space_between_holes) if self.fillHoles_fill_pattern in ["hbar", "vbar"] else min(2*self.thickness, self.width/20), + bspace=min(2*self.thickness, self.fillHoles_space_to_border) if self.fillHoles_fill_pattern in ["hbar", "vbar"] else min(2*self.thickness, self.width/20), + bar_length=self.fillHoles_bar_length, + max_random=self.fillHoles_max_random, + ) + + def cb_bottom_chute(self, nr): + if nr == 1: + # holes for pusher + self.rectangularHole(self.width*0.85-0.5*self.thickness, 0.25*self.pusherA, self.thickness, 0.5*self.pusherA, center_x=False, center_y=False) + self.rectangularHole(self.width*0.5 -0.5*self.thickness, 0.25*self.pusherA, self.thickness, 0.5*self.pusherA, center_x=False, center_y=False) + self.rectangularHole(self.width*0.15-0.5*self.thickness, 0.25*self.pusherA, self.thickness, 0.5*self.pusherA, center_x=False, center_y=False) + + + def cb_back(self, nr): + if nr == 1: + # holes for pusher + self.rectangularHole(self.width*0.85-0.5*self.thickness, self.thickness + self.depth * math.tan(math.radians(self.chuteAngle)) + 0.25*self.pusherB, self.thickness, 0.5*self.pusherB + self.thickness, center_x=False, center_y=False) + self.rectangularHole(self.width*0.5 -0.5*self.thickness, self.thickness + self.depth * math.tan(math.radians(self.chuteAngle)) + 0.25*self.pusherB, self.thickness, 0.5*self.pusherB + self.thickness, center_x=False, center_y=False) + self.rectangularHole(self.width*0.15-0.5*self.thickness, self.thickness + self.depth * math.tan(math.radians(self.chuteAngle)) + 0.25*self.pusherB, self.thickness, 0.5*self.pusherB + self.thickness, center_x=False, center_y=False) + + + def cb_sides(self, nr): + if nr == 0: + # for debugging only + if self.debug: + # draw orientation points + self.hole(0, 0, 1, color=Color.ANNOTATIONS) + self.hole(0, self.thickness, 1, color=Color.ANNOTATIONS) + self.hole(0, self.thickness + self.canDiameter, 1, color=Color.ANNOTATIONS) + self.hole(0, self.thickness + self.canDiameter + self.bottom_chute_height, 1, color=Color.ANNOTATIONS) + self.hole(0, self.thickness + self.canDiameter + self.bottom_chute_height + self.top_chute_height + self.thickness, 1, color=Color.ANNOTATIONS) + self.hole(0, self.thickness + self.canDiameter + self.bottom_chute_height + self.top_chute_height + self.thickness + self.canDiameter, 1, color=Color.ANNOTATIONS) + self.hole(0, self.thickness + self.canDiameter + self.bottom_chute_height + self.top_chute_height + self.thickness + self.canDiameter + 1.0 * self.thickness, 1, color=Color.ANNOTATIONS) + with self.saved_context(): + # draw cans, bottom row + self.moveTo(0, self.thickness, self.chuteAngle) + self.rectangularHole(2*self.thickness, 0, math.ceil(self.canNum / 2) * self.canDiameter, self.canDiameter, center_x=False, center_y=False, color=Color.ANNOTATIONS) + for i in range(math.ceil(self.canNum / 2)-1): + self.hole(2*self.thickness+(0.5 + i) * self.canDiameter, self.canDiameter / 2, self.canDiameter / 2, color=Color.ANNOTATIONS) + i+=1 + self.hole(2*self.thickness+(0.5 + i) * self.canDiameter, self.canDiameter*0.8 , self.canDiameter / 2, color=Color.ANNOTATIONS) + + with self.saved_context(): + # draw pusher + self.moveTo(self.depth-self.pusherA, self.thickness + (self.depth-self.pusherA) * math.tan(math.radians(self.chuteAngle))) + self.moveTo(0,0,self.chuteAngle) + self.DrawPusher(True) + + with self.saved_context(): + # draw cans, top row + self.moveTo(0, self.thickness + self.canDiameter + self.bottom_chute_height + self.top_chute_height + 0.5 * self.thickness, -self.chuteAngle) + self.rectangularHole(0, 0.5 * self.thickness, math.ceil(self.canNum / 2) * self.canDiameter, self.canDiameter, center_x=False, center_y=False, color=Color.ANNOTATIONS) + for i in range(math.ceil(self.canNum / 2)): + self.hole((0.5 + i) * self.canDiameter, self.canDiameter / 2 + 0.5 * self.thickness, self.canDiameter / 2, color=Color.ANNOTATIONS) + with self.saved_context(): + # draw barrier + self.moveTo(1.5 * self.thickness, 1.1 * self.thickness + self.burn + math.sin(math.radians(self.chuteAngle)) * 2 * self.thickness, 90) + self.rectangularHole(0, 0, self.barrier_height, self.thickness, center_x=False, center_y=True, color=Color.ANNOTATIONS) + + # bottom chute + with self.saved_context(): + self.moveTo(0, 0.5 * self.thickness, self.chuteAngle) + self.fingerHolesAt(0, 0, self.depth / math.cos(math.radians(self.chuteAngle)), 0) + # top chute + with self.saved_context(): + self.moveTo(0, self.thickness + self.canDiameter + self.bottom_chute_height + self.top_chute_height + 0.5 * self.thickness, -self.chuteAngle) + self.fingerHolesAt(0, 0, self.top_chute_depth, 0) + # front barrier + with self.saved_context(): + self.moveTo(1.5 * self.thickness, 1.1 * self.thickness + self.burn + math.sin(math.radians(self.chuteAngle)) * 2 * self.thickness, 90) + self.fingerHolesAt(0, 0, self.barrier_height, 0) + # fill with holes + border = [ + (2*self.thickness, 0.5*self.thickness + 2*self.thickness * math.tan(math.radians(self.chuteAngle)) + 0.5*self.thickness/math.cos(math.radians(self.chuteAngle))), + (self.depth, self.thickness + self.depth * math.tan(math.radians(self.chuteAngle))), + (self.depth, self.height), + (self.thickness + 0.75 * self.canDiameter, self.height), + (self.thickness + 0.75 * self.canDiameter, 0.5*self.thickness + self.canDiameter + self.bottom_chute_height + self.top_chute_height + self.thickness - (self.thickness + 0.75 * self.canDiameter) * math.tan(math.radians(self.chuteAngle)) + 0.5*self.thickness/math.cos(math.radians(self.chuteAngle))), + (self.top_chute_depth * math.cos(math.radians(self.chuteAngle)), self.thickness + self.canDiameter + self.bottom_chute_height + self.top_chute_height + self.thickness - (self.top_chute_depth) * math.sin(math.radians(self.chuteAngle))), + (self.top_chute_depth * math.cos(math.radians(self.chuteAngle)), self.thickness + self.canDiameter + self.bottom_chute_height + self.top_chute_height - (self.top_chute_depth) * math.sin(math.radians(self.chuteAngle))), + (self.thickness + 0.75 * self.canDiameter, 1.5*self.thickness + self.canDiameter + self.bottom_chute_height + self.top_chute_height - (self.thickness + 0.75 * self.canDiameter) * math.tan(math.radians(self.chuteAngle)) - 0.5*self.thickness/math.cos(math.radians(self.chuteAngle))), + (self.thickness + 0.75 * self.canDiameter, 2*self.thickness + self.barrier_height ), + (2*self.thickness, 2*self.thickness + self.barrier_height), + ] + + self.fillHoles( + pattern=self.fillHoles_fill_pattern, + border=border, + max_radius=self.fillHoles_hole_max_radius, + hspace=self.fillHoles_space_between_holes, + bspace=self.fillHoles_space_to_border, + min_radius=self.fillHoles_hole_min_radius, + style=self.fillHoles_hole_style, + bar_length=self.fillHoles_bar_length, + max_random=self.fillHoles_max_random, + ) + + def render(self): + self.chuteAngle = self.chuteAngle + + self.pusherAngle = 30 # angle of pusher + self.pusherA = 0.75 * self.canDiameter # length of pusher + self.pusherB = self.pusherA / math.sin(math.radians(180 - (90+self.chuteAngle) - self.pusherAngle)) * math.sin(math.radians(self.pusherAngle)) + self.pusherC = self.pusherA / math.sin(math.radians(180 - (90+self.chuteAngle) - self.pusherAngle)) * math.sin(math.radians(90+self.chuteAngle)) + + self.addPart(FrontEdge(self, self)) + self.addPart(TopChuteEdge(self, self)) + self.addPart(BarrierEdge(self, self)) + + if self.canDiameter < 8 * self.thickness: + self.edges["f"].settings.setValues(self.thickness, True, finger=1.0) + self.edges["f"].settings.setValues(self.thickness, True, space=1.0) + self.edges["f"].settings.setValues(self.thickness, True, surroundingspaces=0.0) + + if self.canDiameter < 4 * self.thickness: + raise ValueError("Can diameter has to be at least 4 times the material thickness!") + + if self.canNum < 4: + raise ValueError("4 cans is the minimum!") + + self.depth = self.canDiameter * (math.ceil(self.canNum / 2) + 0.1) + self.thickness + self.top_chute_height = max(self.depth * math.sin(math.radians(self.chuteAngle)), 0.1 * self.canDiameter) + self.top_chute_depth = (self.depth - 1.1 * self.canDiameter) / math.cos(math.radians(self.chuteAngle)) + self.bottom_chute_height = max((self.depth - 1.1 * self.canDiameter) * math.sin(math.radians(self.chuteAngle)), 0.1 * self.canDiameter) + self.bottom_chute_depth = self.depth / math.cos(math.radians(self.chuteAngle)) + self.barrier_height = 0.25 * self.canDiameter + + if (self.top_chute_depth + self.bottom_chute_height - self.thickness) < (self.barrier_height + self.canDiameter * 0.1): + self.bottom_chute_height = self.barrier_height + self.canDiameter * 0.1 + self.thickness - self.top_chute_depth + + self.height = self.thickness + self.canDiameter + self.bottom_chute_height + self.top_chute_height + 0.5 * self.thickness + self.canDiameter + 1.5 * self.thickness # measurements from bottom to top + self.width = 0.01 * self.canHight + self.canHight + 0.01 * self.canHight + edgs = self.bottom_edge + "h" + self.top_edge + "a" + + # render your parts here + self.rectangularWall(self.depth, self.height, edges=edgs, callback=self.cb_sides, move="up", label="right") + self.rectangularWall(self.depth, self.height, edges=edgs, callback=self.cb_sides, move="up mirror", label="left") + + self.rectangularWall(self.bottom_chute_depth, self.width, "fefe", callback=self.cb_bottom_chute, move="up", label="bottom chute") + self.rectangularWall(self.top_chute_depth, self.width, "fbfe", callback=self.cb_top_chute, move="up", label="top chute") + + self.rectangularWall(self.barrier_height, self.width, "fAfe", move="right", label="barrier") + self.rectangularWall(self.height, self.width, "fefe", callback=self.cb_back, move="up", label="back") + self.rectangularWall(self.barrier_height, self.width, "fefe", move="left only", label="invisible") + + if self.top_edge != "e": + self.rectangularWall(self.depth, self.width, "fefe", callback=self.cb_top, move="up", label="top") + + pusherH = self.pusherB * math.cos(math.radians(self.chuteAngle)) + self.thickness + pusherV = self.pusherC * math.cos(math.radians(self.chuteAngle)) + self.thickness + + self.move(pusherV, pusherH, where ="right", before=True, label="Pusher") + self.DrawPusher() + self.move(pusherV, pusherH, where ="right", before=False, label="Pusher") + self.move(pusherV, pusherH, where ="right", before=True, label="Pusher") + self.DrawPusher() + self.move(pusherV, pusherH, where ="right", before=False, label="Pusher") + self.move(pusherV, pusherH, where ="up", before=True, label="Pusher") + self.DrawPusher() + self.text("Glue the Pusher pieces into slots on bottom\nand back plates to prevent stuck cans.", pusherV+3,0, fontsize=4, color=Color.ANNOTATIONS) + self.move(pusherV, pusherH, where ="up", before=False, label="Pusher") + + self.move(pusherV, pusherH, where ="left only", before=True, label="Pusher") + self.move(pusherV, pusherH, where ="left only", before=True, label="Pusher") + + if self.bottom_edge == "š": + self.rectangularWall(self.edges["š"].settings.width+3*self.thickness, self.edges["š"].settings.height-4*self.burn, "eeee", move="right", label="Stabilizer 1") + self.rectangularWall(self.edges["š"].settings.width+3*self.thickness, self.edges["š"].settings.height-4*self.burn, "eeee", move="right", label="Stabilizer 2") + self.rectangularWall(self.edges["š"].settings.width+5*self.thickness, self.edges["š"].settings.height-4*self.burn, "eeee", move="right", label="Stabilizer 3") + self.rectangularWall(self.edges["š"].settings.width+5*self.thickness, self.edges["š"].settings.height-4*self.burn, "eeee", move="right", label="Stabilizer 4") + self.text("Glue a stabilizer on the inside of each bottom\nside stacking foot for lateral stabilization.",3 ,0 , fontsize=4, color=Color.ANNOTATIONS)
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/cardbox.html b/html/_modules/boxes/generators/cardbox.html new file mode 100644 index 0000000..aed3370 --- /dev/null +++ b/html/_modules/boxes/generators/cardbox.html @@ -0,0 +1,255 @@ + + + + + + + + boxes.generators.cardbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.cardbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+# Copyright (C) 2018 jens persson <jens@persson.cx>
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import edges, Boxes
+
+
+class InsetEdgeSettings(edges.Settings):
+    """Settings for InsetEdge"""
+    absolute_params = {
+        "thickness": 0,
+    }
+
+
+class InsetEdge(edges.BaseEdge):
+    """An edge with space to slide in a lid"""
+    def __call__(self, length, **kw):
+        t = self.settings.thickness
+        self.corner(90)
+        self.edge(t, tabs=2)
+        self.corner(-90)
+        self.edge(length, tabs=2)
+        self.corner(-90)
+        self.edge(t, tabs=2)
+        self.corner(90)
+
+
+class FingerHoleEdgeSettings(edges.Settings):
+    """Settings for FingerHoleEdge"""
+    absolute_params = {
+        "wallheight": 0,
+    }
+
+
+class FingerHoleEdge(edges.BaseEdge):
+    """An edge with room to get your fingers around cards"""
+    def __call__(self, length, **kw):
+        depth = self.settings.wallheight-self.thickness-10
+        self.edge(length/2-10, tabs=2)
+        self.corner(90)
+        self.edge(depth, tabs=2)
+        self.corner(-180, 10)
+        self.edge(depth, tabs=2)
+        self.corner(90)
+        self.edge(length/2-10, tabs=2)
+
+
+
[docs]class CardBox(Boxes): + """Box for storage of playing cards""" + ui_group = "Box" + + description = """ +#### Building instructions + +Place inner walls on floor first (if any). Then add the outer walls. Glue the two walls without finger joins to the inside of the side walls. Make sure there is no squeeze out on top, as this is going to form the rail for the lid. + +Add the top of the rails to the sides and the grip rail to the lid. + +Details of the lid and rails + +![Details](static/samples/CardBox-detail.jpg) + +Whole box (early version still missing grip rail on the lid): +""" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.buildArgParser(h=30) + self.argparser.add_argument( + "--cardwidth", action="store", type=float, default=65, + help="Width of the cards") + self.argparser.add_argument( + "--cardheight", action="store", type=float, default=90, + help="Height of the cards") + self.argparser.add_argument( + "--num", action="store", type=int, default=2, + help="number of compartments") + + @property + def boxwidth(self): + return self.num * (self.cardwidth + self.thickness) + self.thickness + + def divider_bottom(self): + t = self.thickness + c = self.cardwidth + y = self.cardheight + + for i in range(1, self.num): + self.fingerHolesAt(0.5*t + (c+t)*i, 0, y, 90) + + def divider_back_and_front(self): + t = self.thickness + c = self.cardwidth + y = self.h + for i in range(1, self.num): + self.fingerHolesAt(0.5*t + (c+t)*i, 0, y, 90) + + def render(self): + h = self.h + t = self.thickness + + x = self.boxwidth + y = self.cardheight + + s = InsetEdgeSettings(thickness=t) + p = InsetEdge(self, s) + p.char = "a" + self.addPart(p) + + s = FingerHoleEdgeSettings(thickness=t, wallheight=h) + p = FingerHoleEdge(self, s) + p.char = "A" + self.addPart(p) + + with self.saved_context(): + self.rectangularWall(x-t*.2, y, "eeFe", move="right", label="Lid") + self.rectangularWall(x, y, "ffff", callback=[self.divider_bottom], + move="right", label="Bottom") + self.rectangularWall(x, y, "eEEE", move="up only") + self.rectangularWall(x-t*.2, t, "fEeE", move="up", label="Lid Lip") + + with self.saved_context(): + self.rectangularWall(x, h+t, "FFEF", + callback=[self.divider_back_and_front], + move="right", + label="Back") + self.rectangularWall(x, h+t, "FFaF", + callback=[self.divider_back_and_front], + move="right", + label="Front") + self.rectangularWall(x, h+t, "EEEE", move="up only") + + + with self.saved_context(): + self.rectangularWall(y, h+t, "FfFf", move="right", label="Outer Side Left") + self.rectangularWall(y, h+t, "FfFf", move="right", label="Outer Side Right") + self.rectangularWall(y, h+t, "fFfF", move="up only") + + with self.saved_context(): + self.rectangularWall(y, h, "Aeee", move="right", label="Inner Side Left") + self.rectangularWall(y, h, "Aeee", move="right", label="Inner Side Right") + self.rectangularWall(y, h, "eAee", move="up only") + + with self.saved_context(): + self.rectangularWall(y, t, "eefe", move="right", label="Lip Left") + self.rectangularWall(y, t, "feee", move="right", label="Lip Right") + self.rectangularWall(y, t*2, "efee", move="up only") + + for i in range(self.num - 1): + self.rectangularWall(h, y, "fAff", move="right", label="Divider")
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/cardholder.html b/html/_modules/boxes/generators/cardholder.html new file mode 100644 index 0000000..8111a56 --- /dev/null +++ b/html/_modules/boxes/generators/cardholder.html @@ -0,0 +1,202 @@ + + + + + + + + boxes.generators.cardholder — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.cardholder

+#!/usr/bin/env python3
+# Copyright (C) 2013-2021 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
+
[docs]class CardHolder(Boxes): + """Shelf for holding (multiple) piles of playing cards / notes""" + + ui_group = "Shelf" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.GroovedSettings) + self.addSettingsArgs(edges.FingerJointSettings, surroundingspaces=1.0) + + self.buildArgParser(sx="68*3", y=100, h=40, outside=False) + self.argparser.add_argument( + "--angle", action="store", type=float, default=7.5, + help="backward angle of floor") + self.argparser.add_argument( + "--stackable", action="store", type=boolarg, default=True, + help="make holders stackable") + + def side(self): + t = self.thickness + a = math.radians(self.angle) + + pos_y = self.y - abs(0.5 * t * math.sin(a)) + pos_h = t - math.cos(a) * 0.5 * t + self.fingerHolesAt(pos_y, pos_h, self.y, 180-self.angle) + + def fingerHoleCB(self, length, posy=0.0): + def CB(): + t = self.thickness + px = -0.5 * t + for x in self.sx[:-1]: + px += x + t + self.fingerHolesAt(px, posy, length, 90) + return CB + + def middleWall(self, move=None): + y, h = self.y , self.h + a = self.angle + t = self.thickness + tw = y + t + th = h + + if self.move(tw, th, move, True): + return + + self.moveTo(t, t, a) + + self.edges["f"](y) + self.polyline(0, 90-a, h-t-y*math.sin(math.radians(a)), 90, + y*math.cos(math.radians(a)), 90) + self.edges["f"](h-t) + + self.move(tw, th, move) + + def render(self): + sx, y = self.sx, self.y + t = self.thickness + + bottom = "š" if self.stackable else "e" + top = "S" if self.stackable else "e" + + if self.outside: + self.sx = sx = self.adjustSize(sx) + h = self.h = self.adjustSize(self.h, bottom, top) + else: + h = self.h = self.h + t + y * math.sin(math.radians(self.angle)) + self.x = x = sum(sx) + t * (len(sx) - 1) + + self.rectangularWall(y, h, [bottom, "F", top, "e"], + ignore_widths=[1, 6], + callback=[self.side], move="up") + self.rectangularWall(y, h, [bottom, "F", top, "e"], + ignore_widths=[1, 6], + callback=[self.side], move="up mirror") + + nx = len(sx) + f_lengths = [] + for val in self.sx: + f_lengths.append(val) + f_lengths.append(t) + f_lengths = f_lengths[:-1] + + frontedge = edges.CompoundEdge( + self, "e".join("z" * nx), f_lengths) + + self.rectangularWall(x, y, [frontedge, "f", "e", "f"], + callback=[self.fingerHoleCB(y)], move="up") + self.rectangularWall(x, h, bottom + "f" + top + "f", + ignore_widths=[1, 6], + callback=[self.fingerHoleCB(h-t, t)], move="up") + for i in range(nx-1): + self.middleWall(move="right")
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/castle.html b/html/_modules/boxes/generators/castle.html new file mode 100644 index 0000000..3914b91 --- /dev/null +++ b/html/_modules/boxes/generators/castle.html @@ -0,0 +1,138 @@ + + + + + + + + boxes.generators.castle — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.castle

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
+
[docs]class Castle(Boxes): + "Castle tower with two walls" + + description = """This was done as a table decoration. May be at some point in the future someone will create a proper castle +with towers and gates and walls that can be attached in multiple configurations.""" + ui_group = "Unstable" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + + def render(self, t_x=70, t_h=250, w1_x=300, w1_h=120, w2_x=100, w2_h=120): + s = edges.FingerJointSettings(10.0, relative=True, + space=1, finger=1, + width=self.thickness) + + s.edgeObjects(self, "pPQ") + + self.moveTo(0, 0) + self.rectangularWall(t_x, t_h, edges="efPf", move="right", callback=[lambda: self.fingerHolesAt(t_x * 0.5, 0, w1_h, 90), ]) + self.rectangularWall(t_x, t_h, edges="efPf", move="right") + self.rectangularWall(t_x, t_h, edges="eFPF", move="right", callback=[lambda: self.fingerHolesAt(t_x * 0.5, 0, w2_h, 90), ]) + self.rectangularWall(t_x, t_h, edges="eFPF", move="right") + + self.rectangularWall(w1_x, w1_h, "efpe", move="right") + self.rectangularWall(w2_x, w2_h, "efpe", move="right")
+ + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/closedbox.html b/html/_modules/boxes/generators/closedbox.html new file mode 100644 index 0000000..5ce40f4 --- /dev/null +++ b/html/_modules/boxes/generators/closedbox.html @@ -0,0 +1,151 @@ + + + + + + + + boxes.generators.closedbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.closedbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
+
[docs]class ClosedBox(Boxes): + """Fully closed box""" + + ui_group = "Box" + + description = """This box is more of a building block than a finished item. +Use a vector graphics program (like Inkscape) to add holes or adjust the base +plate. + +See BasedBox for variant with a base.""" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.buildArgParser("x", "y", "h", "outside") + + def render(self): + + x, y, h = self.x, self.y, self.h + + if self.outside: + x = self.adjustSize(x) + y = self.adjustSize(y) + h = self.adjustSize(h) + + t = self.thickness + + d2 = edges.Bolts(2) + d3 = edges.Bolts(3) + + d2 = d3 = None + + self.rectangularWall(x, h, "FFFF", bedBolts=[d2] * 4, move="right", label="Wall 1") + self.rectangularWall(y, h, "FfFf", bedBolts=[d3, d2, d3, d2], move="up", label="Wall 2") + self.rectangularWall(y, h, "FfFf", bedBolts=[d3, d2, d3, d2], label="Wall 4") + self.rectangularWall(x, h, "FFFF", bedBolts=[d2] *4, move="left up", label="Wall 3") + + self.rectangularWall(x, y, "ffff", bedBolts=[d2, d3, d2, d3], move="right", label="Top") + self.rectangularWall(x, y, "ffff", bedBolts=[d2, d3, d2, d3], label="Bottom")
+ + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/coffeecapsulesholder.html b/html/_modules/boxes/generators/coffeecapsulesholder.html new file mode 100644 index 0000000..0465623 --- /dev/null +++ b/html/_modules/boxes/generators/coffeecapsulesholder.html @@ -0,0 +1,223 @@ + + + + + + + + boxes.generators.coffeecapsulesholder — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.coffeecapsulesholder

+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2021 Guillaume Collic
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import math
+from boxes import Boxes, boolarg
+
+
+
[docs]class CoffeeCapsuleHolder(Boxes): + """ + Coffee capsule holder + """ + + ui_group = "Misc" + + description = """ + You can store your coffee capsule near your expresso machine with this. It works both vertically, or upside down under a shelf. +""" + + def __init__(self): + Boxes.__init__(self) + self.argparser.add_argument( + "--columns", + type=int, + default=4, + help="Number of columns of capsules.", + ) + self.argparser.add_argument( + "--rows", + type=int, + default=5, + help="Number of capsules by columns.", + ) + self.argparser.add_argument( + "--backplate", + type=boolarg, + default=True, + help="True if a backplate should be generated.", + ) + + def render(self): + self.lid_size = 37 + self.lid_size_with_margin = 39 + self.body_size = 30 + self.column_spacing = 5 + self.corner_radius = 3 + self.screw_margin = 6 + self.outer_margin = 7 + # Add space for the opening. A full row is not necessary for it. + self.rows = self.rows + 0.6 + + self.render_plate(screw_hole=7, hole_renderer=self.render_front_hole) + self.render_plate(hole_renderer=self.render_middle_hole) + if self.backplate: + self.render_plate() + + def render_plate(self, screw_hole=3.5, hole_renderer=None, move="right"): + width = ( + self.columns * (self.lid_size_with_margin + self.column_spacing) + - self.column_spacing + + 2 * self.outer_margin + ) + height = self.rows * self.lid_size + 2 * self.outer_margin + + if self.move(width, height, move, True): + return + + with self.saved_context(): + self.moveTo(self.corner_radius) + self.polyline( + width - 2 * self.corner_radius, + (90, self.corner_radius), + height - 2 * self.corner_radius, + (90, self.corner_radius), + width - 2 * self.corner_radius, + (90, self.corner_radius), + height - 2 * self.corner_radius, + (90, self.corner_radius), + ) + + if hole_renderer: + for col in range(self.columns): + with self.saved_context(): + self.moveTo( + self.outer_margin + col * (self.lid_size_with_margin + self.column_spacing) - self.burn, + self.outer_margin + (self.rows - 0.5) * self.lid_size + self.burn, + -90, + ) + hole_renderer() + + if screw_hole: + for x in [self.screw_margin, width - self.screw_margin]: + for y in [self.screw_margin, height - self.screw_margin]: + self.hole(x, y + self.burn, d=screw_hole) + + self.move(width, height, move) + + def render_front_hole(self): + radians = math.acos(self.body_size / self.lid_size_with_margin) + height_difference = (self.lid_size / 2) * math.sin(radians) + degrees = math.degrees(radians) + half = [ + 0, + (degrees, self.lid_size_with_margin / 2), + 0, + -degrees, + (self.rows - 1) * self.lid_size - height_difference, + ] + path = ( + half + + [(180, self.body_size / 2)] + + list(reversed(half)) + + [(180, self.lid_size_with_margin / 2)] + ) + self.polyline(*path) + + def render_middle_hole(self): + half = [(self.rows - 1) * self.lid_size, (180, self.lid_size_with_margin / 2)] + path = half * 2 + self.polyline(*path)
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/coindisplay.html b/html/_modules/boxes/generators/coindisplay.html new file mode 100644 index 0000000..628b7ee --- /dev/null +++ b/html/_modules/boxes/generators/coindisplay.html @@ -0,0 +1,205 @@ + + + + + + + + boxes.generators.coindisplay — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.coindisplay

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+class CoinHolderSideEdge(edges.BaseEdge):
+    char = "B"
+    def __call__(self, length, **kw):
+        a_l = self.coin_plate
+        a_l2 = self.settings.coin_plate * math.sin(self.settings.angle)
+        a = math.degrees(self.settings.angle)
+
+        print(a, a_l, a_l2)
+        self.corner(-a)
+        # Draw the angled edge, but set the thickness to two temporarily
+        #   as two pieces will go on top of another
+        self.edges["F"].settings.thickness = self.thickness * 2
+        self.edges["F"](a_l)
+        self.edges["F"].settings.thickness = self.thickness
+
+        self.polyline(0, 90+a, a_l2, -90)
+
+    def margin(self):
+        return self.settings.coin_plate_x
+
+
[docs]class CoinDisplay(Boxes): + """A showcase for a single coin""" + + ui_group = "Misc" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings) + self.buildArgParser("x", "y", "h", "outside") + self.argparser.add_argument( + "--coin_d", action="store", type=float, default=20.0, + help="The diameter of the coin in mm") + self.argparser.add_argument( + "--coin_plate", action="store", type=float, default=50.0, + help="The size of the coin plate") + self.argparser.add_argument( + "--coin_showcase_h", action="store", type=float, default=50.0, + help="The height of the coin showcase piece") + self.argparser.add_argument( + "--angle", action="store", type=float, default=30, + help="The angle that the coin will tilt as") + + def bottomHoles(self): + """ + Function that puts two finger holes at the bottom cube plate for the coin holder + """ + self.fingerHolesAt(self.x/2 - self.thickness - self.thickness/2 - (self.coin_plate/2), self.y/2+self.coin_plate_x/2-self.thickness, self.coin_plate_x, -90) + self.fingerHolesAt(self.x/2 - self.thickness + self.thickness/2 + (self.coin_plate/2), self.y/2+self.coin_plate_x/2-self.thickness, self.coin_plate_x, -90) + + self.fingerHolesAt(self.x/2-self.coin_plate/2-self.thickness, self.y/2-self.coin_plate_x/2-self.thickness*1.5, self.coin_plate, 0) + + def coinCutout(self): + """ + Function that puts a circular hole in the coin holder piece + """ + self.hole(self.coin_plate/2, self.coin_plate/2, self.coin_d/2) + + def render(self): + + x, y, h = self.x, self.y, self.h + + if self.outside: + x = self.adjustSize(x) + y = self.adjustSize(y) + h = self.adjustSize(h) + + t = self.thickness + + d2 = edges.Bolts(2) + d3 = edges.Bolts(3) + + d2 = d3 = None + + self.addPart(CoinHolderSideEdge(self, self)) + + self.angle = math.radians(self.angle) + self.coin_plate_x = self.coin_plate * math.cos(self.angle) + + self.rectangularWall(x, h, "FFFF", bedBolts=[d2] * 4, move="right", label="Wall 1") + self.rectangularWall(y, h, "FfFf", bedBolts=[d3, d2, d3, d2], move="up", label="Wall 2") + self.rectangularWall(y, h, "FfFf", bedBolts=[d3, d2, d3, d2], label="Wall 4") + self.rectangularWall(x, h, "FFFF", bedBolts=[d2] *4, move="left up", label="Wall 3") + + self.rectangularWall(x, y, "ffff", bedBolts=[d2, d3, d2, d3], move="right", label="Top") + self.rectangularWall(x, y, "ffff", bedBolts=[d2, d3, d2, d3], move="right", label="Bottom", callback=[self.bottomHoles]) + + # Draw the coin holder side holsers + e = ["f", "f", "B", "e"] + self.rectangularWall(self.coin_plate_x, self.coin_showcase_h, e, move="right", label="CoinSide1") + self.rectangularWall(self.coin_plate_x, self.coin_showcase_h, e, move="right", label="CoinSide2") + + self.rectangularWall(self.coin_plate, self.coin_plate, "efef", move="left down", label="Coin Plate Base") + self.rectangularWall(self.coin_plate, self.coin_plate, "efef", move="down", label="Coin Plate", callback=[self.coinCutout]) + + self.rectangularWall(self.coin_plate, self.coin_showcase_h, "fFeF", move="down", label="CoinSide3")
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/concaveknob.html b/html/_modules/boxes/generators/concaveknob.html new file mode 100644 index 0000000..a823900 --- /dev/null +++ b/html/_modules/boxes/generators/concaveknob.html @@ -0,0 +1,155 @@ + + + + + + + + boxes.generators.concaveknob — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.concaveknob

+#!/usr/bin/env python3
+# Copyright (C) 2013-2017 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class ConcaveKnob(Boxes): + """Round knob serrated outside for better gripping""" + + ui_group = "Part" + + def __init__(self): + Boxes.__init__(self) + + # Add non default cli params if needed (see argparse std lib) + self.argparser.add_argument( + "--diameter", action="store", type=float, default=50., + help="Diameter of the knob (mm)") + self.argparser.add_argument( + "--serrations", action="store", type=int, default=3, + help="Number of serrations") + self.argparser.add_argument( + "--rounded", action="store", type=float, default=.2, + help="Amount of circumference used for non convex parts") + self.argparser.add_argument( + "--angle", action="store", type=float, default=70., + help="Angle between convex and concave parts") + self.argparser.add_argument( + "--bolthole", action="store", type=float, default=6., + help="Diameter of the bolt hole (mm)") + self.argparser.add_argument( + "--dhole", action="store", type=float, default=1., + help="D-Flat in fraction of the diameter") + self.argparser.add_argument( + "--hexhead", action="store", type=float, default=10., + help="Width of the hex bolt head (mm)") + + def render(self): + t = self.thickness + self.parts.concaveKnob(self.diameter, self.serrations, + self.rounded, self.angle, + callback=lambda:self.dHole(0, 0, + d=self.bolthole, + rel_w=self.dhole), + move="right") + self.parts.concaveKnob(self.diameter, self.serrations, + self.rounded, self.angle, + callback=lambda: self.nutHole(self.hexhead), + move="right") + self.parts.concaveKnob(self.diameter, self.serrations, + self.rounded, self.angle)
+ + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/console.html b/html/_modules/boxes/generators/console.html new file mode 100644 index 0000000..3d030aa --- /dev/null +++ b/html/_modules/boxes/generators/console.html @@ -0,0 +1,153 @@ + + + + + + + + boxes.generators.console — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.console

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class Console(Boxes): + """Console with slanted panel""" + + ui_group = "Box" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings, surroundingspaces=.5) + self.addSettingsArgs(edges.StackableSettings) + + self.buildArgParser(x=100, y=100, h=100, outside=False) + self.argparser.add_argument( + "--front_height", action="store", type=float, default=30, + help="height of the front below the panel (in mm)") + self.argparser.add_argument( + "--angle", action="store", type=float, default=50, + help="angle of the front panel (90°=upright)") + + def render(self): + x, y, h, hf = self.x, self.y, self.h, self.front_height + t = self.thickness + + if self.outside: + self.x = x = self.adjustSize(x) + self.y = y = self.adjustSize(y) + self.h = h = self.adjustSize(h) + + panel = min((h-hf)/math.cos(math.radians(90-self.angle)), + y/math.cos(math.radians(self.angle))) + top = y - panel * math.cos(math.radians(self.angle)) + h = hf + panel * math.sin(math.radians(self.angle)) + + if top>0.1*t: + borders = [y, 90, hf, 90-self.angle, panel, self.angle, top, + 90, h, 90] + else: + borders = [y, 90, hf, 90-self.angle, panel, self.angle+90, h, 90] + + if hf < 0.01*t: + borders[1:4] = [180-self.angle] + + self.polygonWall(borders, move="right") + self.polygonWall(borders, move="right") + self.polygonWalls(borders, x)
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/console2.html b/html/_modules/boxes/generators/console2.html new file mode 100644 index 0000000..cac925b --- /dev/null +++ b/html/_modules/boxes/generators/console2.html @@ -0,0 +1,392 @@ + + + + + + + + boxes.generators.console2 — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.console2

+#!/usr/bin/env python3
+# Copyright (C) 2013-2020 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class Console2(Boxes): + """Console with slanted panel and service hatches""" + + ui_group = "Box" + + description = """ +This box is designed as a housing for electronic projects. It has hatches that can be re-opened with simple tools. It intentionally cannot be opened with bare hands - if build with thin enough material. + +#### Caution +There is a chance that the latches of the back wall or the back wall itself interfere with the front panel or it's mounting frame/lips. The generator does not check for this. So depending on the variant choosen you might need to make the box deeper (increase y parameter) or the panel angle steeper (increase angle parameter) until there is enough room. + +It's also possible that the frame of the panel interferes with the floor if the hi parameter is too small. + +#### Assembly instructions +The main body is easy to assemble by starting with the floor and then adding the four walls and (if present) the top piece. + +If the back wall is removable you need to add the lips and latches. The U-shaped clamps holding the latches in place need to be clued in place without also gluing the latches themselves. Make sure the springs on the latches point inwards and the angled ends point to the side walls as shown here: + +![Back wall details](static/samples/Console2-backwall-detail.jpg) + +If the panel is removable you need to add the springs with the tabs to the side lips. This photo shows the variant which has the panel glued to the frame: + +![Back wall details](static/samples/Console2-panel-detail.jpg) + +If space is tight you may consider not glueing the cross pieces in place and remove them after the glue-up. This may prevent the latches of the back wall and the panel from interfereing with each other. + +The variant using finger joints only has the two side lips without the cross bars. + +#### Re-Opening + +The latches at the back wall lock in place when closed. To open them they need to be pressed in and can then be moved aside. + +To remove the panel you have to press in the four tabs at the side. It is easiest to push them in and then pull the panel up a little bit so the tabs stay in. +""" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings, surroundingspaces=.5) + self.addSettingsArgs(edges.StackableSettings) + + self.buildArgParser(x=100, y=100, h=100, bottom_edge="s", + outside=False) + self.argparser.add_argument( + "--front_height", action="store", type=float, default=30, + help="height of the front below the panel (in mm)") + self.argparser.add_argument( + "--angle", action="store", type=float, default=50, + help="angle of the front panel (90°=upright)") + self.argparser.add_argument( + "--removable_backwall", action="store", type=boolarg, default=True, + help="have latches at the backwall") + self.argparser.add_argument( + "--removable_panel", action="store", type=boolarg, default=True, + help="The panel is held by tabs and can be removed") + self.argparser.add_argument( + "--glued_panel", action="store", type=boolarg, default=True, + help="the panel is glued and not held by finger joints") + + def borders(self): + x, y, h, fh = self.x, self.y, self.h, self.front_height + t = self.thickness + + panel = min((h-fh)/math.cos(math.radians(90-self.angle)), + y/math.cos(math.radians(self.angle))) + top = y - panel * math.cos(math.radians(self.angle)) + h = fh + panel * math.sin(math.radians(self.angle)) + + if top>0.1*t: + borders = [y, 90, fh, 90-self.angle, panel, self.angle, top, + 90, h, 90] + else: + borders = [y, 90, fh, 90-self.angle, panel, self.angle+90, h, 90] + return borders + + def latch(self, move=None): + t = self.thickness + s = 0.1 * t + + tw, th = 8*t, 3*t + + if self.move(tw, th, move, True): + return + + self.moveTo(0, 1.2*t) + self.polyline(t, -90, .2*t, 90, 2*t, -90, t, 90, t, 90, t, -90, 3*t, + 90, t, -90, t, 90, t, 90, 2*t, 90, 0.5*t, + -94, 4.9*t, 94, .5*t, 86, 4.9*t, -176, 5*t, + -90, 1.0*t, 90, t, 90, 1.8*t, 90) + + self.move(tw, th, move) + + def latch_clamp(self, move=None): + t = self.thickness + s = 0.1 * t + + tw, th = 4*t, 4*t + + if self.move(tw, th, move, True): + return + + self.moveTo(0.5*t) + self.polyline(t-0.5*s, 90, 2.5*t+.5*s, -90, t+s, -90, 2.5*t+.5*s, 90, t-0.5*s, 90, + t, -90, 0.5*t, 90, 2*t, 45, 2**.5*t, 45, 2*t, 45, 2**.5*t, 45, 2*t, 90, 0.5*t, -90, t, 90) + + self.move(tw, th, move) + + @restore + @holeCol + def latch_hole(self, posx): + t = self.thickness + s = 0.1 * t + + self.moveTo(posx, 2*t, 180) + + path = [1.5*t, -90, t, -90, t-0.5*s, 90] + path = path + [2*t] + list(reversed(path)) + path = path[:-1] + [3*t] + list(reversed(path[:-1])) + + self.polyline(*path) + + def panel_side(self, l, move=None): + t = self.thickness + s = 0.1 * t + + tw, th = l, 3*t + + if not self.glued_panel: + th += t + + if self.move(tw, th, move, True): + return + + self.rectangularHole(3*t, 1.5*t, 3*t, 1.05*t) + self.rectangularHole(l-3*t, 1.5*t, 3*t, 1.05*t) + self.rectangularHole(l/2, 1.5*t, 2*t, t) + if self.glued_panel: + self.polyline(*([l, 90, t, 90, t, -90, t, -90, t, 90, t, 90]*2)) + else: + self.polyline(l, 90, 3*t, 90) + self.edges["f"](l) + self.polyline(0, 90, 3*t, 90) + self.move(tw, th, move) + + def panel_lock(self, l, move=None): + t = self.thickness + + l -= 4*t + tw, th = l, 2.5*t + + if self.move(tw, th, move, True): + return + + end = [l/2-3*t, -90, 1.5*t, (90, .5*t), t, (90, .5*t), + t, 90, .5*t, -90, 0.5*t, -90, 0, (90, .5*t), 0, 90,] + + self.moveTo(l/2-t, 2*t, -90) + self.polyline(*([t, 90, 2*t, 90, t, -90] + end + [l] + + list(reversed(end)))) + self.move(tw, th, move) + + def panel_cross_beam(self, l, move=None): + t = self.thickness + + tw, th = l+2*t, 3*t + + if self.move(tw, th, move, True): + return + + self.moveTo(t, 0) + self.polyline(*([l, 90, t, -90, t, 90, t, 90, t, -90, t, 90]*2)) + + self.move(tw, th, move) + + def side(self, borders, bottom="s", move=None, label=""): + + t = self.thickness + bottom = self.edges.get(bottom, bottom) + + tw = borders[0] + 2* self.edges["f"].spacing() + th = borders[-2] + bottom.spacing() + self.edges["f"].spacing() + if self.move(tw, th, move, True): + return + + d1 = t * math.cos(math.radians(self.angle)) + d2 = t * math.sin(math.radians(self.angle)) + + self.moveTo(t, 0) + bottom(borders[0]) + self.corner(90) + self.edges["f"](borders[2]+bottom.endwidth()-d1) + self.edge(d1) + self.corner(borders[3]) + if self.removable_panel: + self.rectangularHole(3*t, 1.5*t, 2.5*t, 1.05*t) + if not self.removable_panel and not self.glued_panel: + self.edges["f"](borders[4]) + else: + self.edge(borders[4]) + if self.removable_panel: + self.rectangularHole(-3*t, 1.5*t, 2.5*t, 1.05*t) + if len(borders) == 10: + self.corner(borders[5]) + self.edge(d2) + self.edges["f"](borders[6]-d2) + self.corner(borders[-3]) + if self.removable_backwall: + self.rectangularHole(self.latchpos, 1.55*t, 1.1*t, 1.1*t) + self.edge(borders[-2]-t) + self.edges["f"](t+bottom.startwidth()) + else: + self.edges["f"](borders[-2]+bottom.startwidth()) + self.corner(borders[-1]) + + self.move(tw, th, move, label=label) + + def render(self): + x, y, h = self.x, self.y, self.h + t = self.thickness + bottom = self.edges.get(self.bottom_edge) + + if self.outside: + self.x = x = self.adjustSize(x) + self.y = y = self.adjustSize(y) + self.h = h = self.adjustSize(h, bottom) + + d1 = t * math.cos(math.radians(self.angle)) + d2 = t * math.sin(math.radians(self.angle)) + + self.latchpos = latchpos = 6*t + + borders = self.borders() + self.side(borders, bottom, move="right", label="Left Side") + self.side(borders, bottom, move="right", label="Right Side") + + self.rectangularWall(borders[0], x, "ffff", move="right", label="Floor") + self.rectangularWall( + borders[2]-d1, x, ("F", "e", "F", bottom), ignore_widths=[7, 4], + move="right", label="Front") + + if self.glued_panel: + self.rectangularWall(borders[4], x, "EEEE", move="right", label="Panel") + elif self.removable_panel: + self.rectangularWall(borders[4], x-2*t, "hEhE", move="right", label="Panel") + else: + self.rectangularWall(borders[4], x, "FEFE", move="right", label="Panel") + + if len(borders) == 10: + self.rectangularWall(borders[6]-d2, x, "FEFe", move="right", label="Top") + + if self.removable_backwall: + self.rectangularWall( + borders[-2]-1.05*t, x, "EeEe", + callback=[ + lambda:self.latch_hole(latchpos), + lambda: self.fingerHolesAt(.5*t, 0, borders[-2]-4.05*t-latchpos), + lambda:self.latch_hole(borders[-2]-1.2*t-latchpos), + lambda: self.fingerHolesAt(.5*t, 3.05*t+latchpos, borders[-2]-4.05*t-latchpos)], + move="right", + label="Back Wall") + self.rectangularWall(2*t, borders[-2]-4.05*t-latchpos, "EeEf", move="right", label="Guide") + self.rectangularWall(2*t, borders[-2]-4.05*t-latchpos, "EeEf", move="right", label="Guide") + self.rectangularWall(t, x, ("F", bottom, "F", "e"), + ignore_widths=[0, 3], move="right", label="Bottom Back") + else: + self.rectangularWall(borders[-2], x, ("F", bottom, "F", "e"), + ignore_widths=[0, 3], move="right", label="Back Wall") + + # hardware for panel + if self.removable_panel: + if self.glued_panel: + self.panel_cross_beam(x-2.05*t, "rotated right") + self.panel_cross_beam(x-2.05*t, "rotated right") + + self.panel_lock(borders[4], "up") + self.panel_lock(borders[4], "up") + self.panel_side(borders[4], "up") + self.panel_side(borders[4], "up") + + # hardware for back wall + if self.removable_backwall: + self.latch(move="up") + self.latch(move="up") + self.partsMatrix(4, 2, "up", self.latch_clamp)
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/dicebox.html b/html/_modules/boxes/generators/dicebox.html new file mode 100644 index 0000000..fe52672 --- /dev/null +++ b/html/_modules/boxes/generators/dicebox.html @@ -0,0 +1,214 @@ + + + + + + + + boxes.generators.dicebox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.dicebox

+#!/usr/bin/env python3
+# Copyright (C) 2022 Erik Snider (SniderThanYou@gmail.com)
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
+
[docs]class DiceBox(Boxes): + """Box with lid and integraded hinge for storing dice""" + + ui_group = "Box" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs( + edges.FingerJointSettings, + surroundingspaces=2.0) + self.addSettingsArgs( + edges.ChestHingeSettings, + finger_joints_on_box=True, + finger_joints_on_lid=True) + self.buildArgParser( + x=100, + y=100, + h=18, + outside=True) + self.argparser.add_argument( + "--lidheight", action="store", type=float, default=18, + help="height of lid in mm") + self.argparser.add_argument( + "--hex_hole_corner_radius", action="store", type=float, default=5, + help="The corner radius of the hexagonal dice holes, in mm") + self.argparser.add_argument( + "--magnet_diameter", action="store", type=float, default=6, + help="The diameter of magnets for holding the box closed, in mm") + + def diceCB(self): + t = self.thickness + xi = self.x - 2 * t + yi = self.y - 2 * t + xc = xi / 2 + yc = yi / 2 + cr = self.hex_hole_corner_radius + + # -4*t because there are four gaps across: + # 2 between the outer holes and the finger joints + # 2 between the outer holes and the center hole + # /6 because there are 6 apothems across, 2 for each hexagon + apothem = (min(xi, yi) - 4 * t) / 6 + r = apothem * 2 / math.sqrt(3) + + # dice + centers = [[xc, yc]] # start with one in the center + polar_r = 2 * apothem + t # the full width of a hexagon, plus a gap of t width + for i in range(6): + theta = i * math.pi / 3 # 60 degrees each step + centers.append( + [ + xc + polar_r * math.cos(theta), + yc + polar_r * math.sin(theta), + ] + ) + for center in centers: + self.regularPolygonHole(x=center[0], y=center[1], n=6, r=r, corner_radius=cr, a=30) + + # magnets + d = self.magnet_diameter + mo = t + d/2 + self.hole(mo, mo, d=d) + self.hole(xi-mo, mo, d=d) + + def render(self): + x, y, h, hl = self.x, self.y, self.h, self.lidheight + + if self.outside: + x = self.adjustSize(x) + y = self.adjustSize(y) + h = self.adjustSize(h) + hl = self.adjustSize(hl) + + t = self.thickness + + hy = self.edges["O"].startwidth() + hy2 = self.edges["P"].startwidth() + + e1 = edges.CompoundEdge(self, "eF", (hy-t, h-hy+t)) + e2 = edges.CompoundEdge(self, "Fe", (h-hy+t, hy-t)) + e_back = ("F", e1, "F", e2) + + p = self.edges["o"].settings.pin_height + e_inner_1 = edges.CompoundEdge(self, "fe", (y-p, p)) + e_inner_2 = edges.CompoundEdge(self, "ef", (p, y-p)) + e_inner_topbot = ("f", e_inner_1, "f", e_inner_2) + + self.ctx.save() + + self.rectangularWall(x, y, e_inner_topbot, move="up", callback=[self.diceCB]) + self.rectangularWall(x, y, e_inner_topbot, move="up", callback=[self.diceCB]) + self.rectangularWall(x, h, "FFFF", ignore_widths=[1,2,5,6], move="up") + self.rectangularWall(x, h, e_back, move="up") + self.rectangularWall(x, hl, "FFFF", ignore_widths=[1,2,5,6], move="up") + self.rectangularWall(x, hl-hy2+t, "FFqF", move="up") + + self.ctx.restore() + self.rectangularWall(x, y, "ffff", move="right only") + + self.rectangularWall(y, x, "ffff", move="up") + self.rectangularWall(y, x, "ffff", move="up") + self.rectangularWall(y, hl-hy2+t, "Ffpf", ignore_widths=[5,6], move="up") + self.rectangularWall(y, h-hy+t, "OfFf", ignore_widths=[5,6], move="up") + self.rectangularWall(y, h-hy+t, "Ffof", ignore_widths=[5,6], move="up") + self.rectangularWall(y, hl-hy2+t, "PfFf", ignore_widths=[5,6], move="up")
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/dinrailbox.html b/html/_modules/boxes/generators/dinrailbox.html new file mode 100644 index 0000000..590f283 --- /dev/null +++ b/html/_modules/boxes/generators/dinrailbox.html @@ -0,0 +1,239 @@ + + + + + + + + boxes.generators.dinrailbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.dinrailbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2020 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
+class DinRailEdge(edges.FingerHoleEdge):
+
+    def __init__(self, boxes, settings, width=35.0, offset=0.0):
+        super().__init__(boxes, settings)
+        self.width = width
+        self.offset = offset
+
+    def startwidth(self):
+        return 8 + self.settings.thickness
+
+    def __call__(self, length, bedBolts=None, bedBoltSettings=None, **kw):
+        with self.saved_context():
+            self.fingerHoles(
+                0, self.burn + 8 + self.settings.thickness / 2, length, 0,
+                bedBolts=bedBolts, bedBoltSettings=bedBoltSettings)
+
+        w = self.width
+        o = self.offset
+        l = length
+        self.polyline((l-w)/2-o, 45, 2.75*2**.5, 90, 2.75*2**.5, -45, .5, -90,
+                      w+0.25,
+                      -90, 1, 30, 5*2*3**-.5, 60, (l-w)/2+o-3.25)
+
+        
+
[docs]class DinRailBox(Boxes): + """Box for DIN rail used in electrical junction boxes""" + + ui_group = "WallMounted" + + def latch(self, l, move=None): + + t = self.thickness + tw = l+3+6+t + th = 8 + + if self.move(tw, th, move, True): + return + + self.moveTo(tw, th, 180) + self.polyline(2, 90, 0, (-180, 1.5), 0, 90, l+1.2*t, 90, + 3, -90, 1, 30, 2*2*3**-.5, 90, 4.5*2*3**-.5, 60, + 4+1.25, 90, 4.5, -90, t+4, -90, 2, 90, l-.8*t-9, 90, 2, -90, 5+t, 90, 4, 90) + + self.move(tw, th, move) + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings, surroundingspaces=.8) + + self.buildArgParser(x=70, y=90, h=60) + # Add non default cli params if needed (see argparse std lib) + self.argparser.add_argument( + "--rail_width", action="store", type=float, default=35., + help="width of the rail (typically 35 or 15mm)") + self.argparser.add_argument( + "--rail_offset", action="store", type=float, default=0., + help="offset of the rail from the middle of the box (in mm)") + + def spring(self): + t = self.thickness + l = min(self.x/2-1.5*t, 50) + self.moveTo(self.x/2-l, -6-t, 0) + self.polyline(l+0.525*t, 90 , 6, 90 , 1.1*t, 90, 3, -90, l-0.525*t, + 180, l-0.525*t, -90, 1+0.1*t, 90, t-0.5, -90, 2) + + def lid_lip(self, l, move=None): + t = self.thickness + tw, th = l+2, t+8 + + if self.move(tw, th, move, True): + return + self.moveTo(1, t) + self.edges["f"](l) + + poly = [0, 90, 6, -60, 0, (120, 2*3**-.5), 0, 30, 2, 90, 5, + (-180, .5), 5, 90] + self.polyline(*(poly+[l-2*3]+list(reversed(poly)))) + + self.move(tw, th, move) + + def lid_holes(self): + t = self.thickness + self.rectangularHole(0.55*t, 7, 1.1*t, 1.6) + self.rectangularHole(self.x-0.55*t, 7, 1.1*t, 1.6) + + def render(self): + # adjust to the variables you want in the local scope + x, y, h = self.x, self.y, self.h + w = self.rail_width + o = self.rail_offset + t = self.thickness + + self.rectangularWall(x, y, "EEEE", callback=[ + lambda:self.fingerHolesAt(.55*t, .05*t, y-.1*t, 90), None, + lambda:self.fingerHolesAt(.55*t, .05*t, y-.1*t, 90), None], + move="right", label="Lid") + + self.lid_lip(y-.1*t, move="rotated right") + self.lid_lip(y-.1*t, move="rotated right") + + self.rectangularWall(x, y, "ffff", + callback=[ + lambda:self.fingerHolesAt(0, (y-w)/2-0.5*t+o-9, x, 0)], + move="right", label="Back") + + # Change h edge to 8mm! + self.edges["f"].settings.setValues(t, False, edge_width=8) + dr = DinRailEdge(self, self.edges["f"].settings, w, o) + + self.rectangularWall(y, h, [dr, "F", "e", "F"], + ignore_widths=[1, 6], move="rotated right", + label="Left Side upsidedown") + self.rectangularWall(y, h, [dr, "F", "e", "F"], + ignore_widths=[1, 6], move="rotated mirror right", + label="Right Side") + self.rectangularWall(x, h, ["h", "f", "e", "f"], + ignore_widths=[1, 6], callback=[ + self.spring, None, self.lid_holes], + move="up", + label="Bottom") + self.rectangularWall(x, h, ["h", "f", "e", "f"], + callback=[None, None, self.lid_holes], + ignore_widths=[1, 6], move="up", + label="Top") + + + self.rectangularWall(x, 8, "feee", callback=[ + lambda:self.rectangularHole(x/2, 2.05-0.5*t, t, t+4.1)], move="up") + self.latch((y-w)/2+o, move="up")
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/discrack.html b/html/_modules/boxes/generators/discrack.html new file mode 100644 index 0000000..947fadb --- /dev/null +++ b/html/_modules/boxes/generators/discrack.html @@ -0,0 +1,367 @@ + + + + + + + + boxes.generators.discrack — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.discrack

+#!/usr/bin/env python3
+# coding: utf-8
+# Copyright (C) 2019 chrysn <chrysn@fsfe.org>
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import division, unicode_literals
+
+from boxes import *
+from math import sqrt, pi, sin, cos
+
+def offset_radius_in_square(squareside, angle, outset):
+    """From the centre of a square, rotate by an angle relative to the
+    vertical, move away from the center (down if angle = 0), and then in a
+    right angle until the border of the square. Return the length of that last
+    segment.
+
+    Note that for consistency with other boxes.py methods, angle is given in
+    degree.
+
+    >>> # Without rotation, it's always half the square length
+    >>> offset_radius_in_square(20, 0, 0)
+    10.0
+    >>> offset_radius_in_square(20, 0, 5)
+    10.0
+    >>> # Without offset, it's half squre length divided by cos(angle) -- at
+    >>> # least before it hits the next wall
+    >>> offset_radius_in_square(20, 15, 0) # doctest:+ELLIPSIS
+    10.35276...
+    >>> offset_radius_in_square(20, 45, 0) # doctest:+ELLIPSIS
+    14.1421...
+    >>> # Positive angles make the segment initially shorter...
+    >>> offset_radius_in_square(20, 5, 10) < 10
+    True
+    >>> # ... while negative angles make it longer.
+    >>> offset_radius_in_square(20, -5, 10) > 10
+    True
+    """
+
+    if angle <= -90:
+        return offset_radius_in_square(squareside, angle + 180, outset)
+    if angle > 90:
+        return offset_radius_in_square(squareside, angle - 180, outset)
+
+    angle = angle / 180 * pi
+
+    step_right = outset * sin(angle)
+    step_down = outset * cos(angle)
+
+    try:
+        len_right = (squareside / 2 - step_right) / cos(angle)
+    except ZeroDivisionError:
+        return squareside / 2
+
+    if angle == 0:
+        return len_right
+    if angle > 0:
+        len_up = (squareside / 2 + step_down) / sin(angle)
+
+        return min(len_up, len_right)
+    else: # angle < 0
+        len_down = - (squareside / 2 - step_down) / sin(angle)
+
+        return min(len_down, len_right)
+
+
[docs]class DiscRack(Boxes): + """A rack for storing disk-shaped objects vertically next to each other""" + + ui_group = "Shelf" + + def __init__(self): + Boxes.__init__(self) + + self.buildArgParser(sx="20*10") + self.argparser.add_argument( + "--disc_diameter", action="store", type=float, default=150.0, + help="Disc diameter in mm") + self.argparser.add_argument( + "--disc_thickness", action="store", type=float, default=5.0, + help="Thickness of the discs in mm") + + self.argparser.add_argument( + "--lower_factor", action="store", type=float, default=0.75, + help="Position of the lower rack grids along the radius") + self.argparser.add_argument( + "--rear_factor", action="store", type=float, default=0.75, + help="Position of the rear rack grids along the radius") + + self.argparser.add_argument( + "--disc_outset", action="store", type=float, default=3.0, + help="Additional space kept between the disks and the outbox of the rack") + + # These can be parameterized, but the default value of pulling them up + # to the box front is good enough for so many cases it'd only clutter + # the user interface. + # + # The parameters can be resurfaced when there is something like rare or + # advanced settings. + ''' + self.argparser.add_argument( + "--lower_outset", action="store", type=float, default=0.0, + help="Space in front of the disk slits (0: automatic)") + self.argparser.add_argument( + "--rear_outset", action="store", type=float, default=0.0, + help="Space above the disk slits (0: automatic)") + ''' + + self.argparser.add_argument( + "--angle", action="store", type=float, default=18, + help="Backwards slant of the rack") + self.addSettingsArgs(edges.FingerJointSettings) + + def parseArgs(self, *args, **kwargs): + Boxes.parseArgs(self, *args, **kwargs) + self.lower_outset = self.rear_outset = 0 + + self.calculate() + + def calculate(self): + self.outer = self.disc_diameter + 2 * self.disc_outset + + r = self.disc_diameter / 2 + + # distance between radius line and front (or rear) end of the slit + self.lower_halfslit = r * sqrt(1 - self.lower_factor**2) + self.rear_halfslit = r * sqrt(1 - self.rear_factor**2) + + if True: # self.lower_outset == 0: # when lower_outset parameter is re-enabled + toplim = offset_radius_in_square(self.outer, self.angle, r * self.lower_factor) + # With typical positive angles, the lower surface of board will be limiting + bottomlim = offset_radius_in_square(self.outer, self.angle, r * self.lower_factor + self.thickness) + self.lower_outset = min(toplim, bottomlim) - self.lower_halfslit + + if True: # self.rear_outset == 0: # when rear_outset parameter is re-enabled + # With typical positive angles, the upper surface of board will be limiting + toplim = offset_radius_in_square(self.outer, -self.angle, r * self.rear_factor) + bottomlim = offset_radius_in_square(self.outer, -self.angle, r * self.rear_factor + self.thickness) + self.rear_outset = min(toplim, bottomlim) - self.rear_halfslit + + # front outset, space to radius, space to rear part, plus nothing as fingers extend out + self.lower_size = self.lower_outset + \ + self.lower_halfslit + \ + r * self.rear_factor + + self.rear_size = r * self.lower_factor + \ + self.rear_halfslit + \ + self.rear_outset + + self.warn_on_demand() + + def warn_on_demand(self): + warnings = [] + + # Are the discs supported on the outer ends? + + def word_thickness(length): + if length > 0: + return "very thin (%.2g mm at a thickness of %.2g mm)" % ( + length, self.thickness) + if length < 0: + return "absent" + + if self.rear_outset < self.thickness: + warnings.append("Rear upper constraint is %s. Consider increasing" + " the disc outset parameter, or move the angle away from 45°." + % word_thickness(self.rear_outset) + ) + + if self.lower_outset < self.thickness: + warnings.append("Lower front constraint is %s. Consider increasing" + " the disc outset parameter, or move the angle away from 45°." + % word_thickness(self.lower_outset)) + + # Are the discs supported where the grids meet? + + r = self.disc_diameter / 2 + inner_lowerdistance = r * self.rear_factor - self.lower_halfslit + inner_reardistance = r * self.lower_factor - self.rear_halfslit + + if inner_lowerdistance < 0 or inner_reardistance < 0: + warnings.append("Corner is inside the disc radios, discs would not" + " be supported. Consider increasing the factor parameters.") + + # Won't the type-H edge on the rear side make the whole contraption + # wiggle? + + max_slitlengthplush = offset_radius_in_square( + self.outer, self.angle, r * self.rear_factor + self.thickness) + slitlengthplush = self.rear_halfslit + self.thickness * ( 1 + \ + self.edgesettings['FingerJoint']['edge_width']) + + if slitlengthplush > max_slitlengthplush: + warnings.append("Joint would protrude from lower box edge. Consider" + " increasing the the disc outset parameter, or move the" + " angle away from 45°.") + + # Can the discs be removed at all? + # Does not need explicit checking, for Thales' theorem tells us that at + # the point wher there is barely support in the corner, three contact + # points on the circle form just a demicircle and the discs can be + # inserted/removed. When we keep the other contact points and move the + # slits away from the corner, the disc gets smaller and thus will fit + # through the opening that is as wide as the diameter of the largest + # possible circle. + + # Act on warnings + + if warnings: + self.argparser.error("\n".join(warnings)) + + def sidewall_holes(self): + r = self.disc_diameter / 2 + + self.moveTo(self.outer/2, self.outer/2, -self.angle) + # can now move down to paint horizontal lower part, or right to paint + # vertical rear part + with self.saved_context(): + self.moveTo( + r * self.rear_factor, + -r * self.lower_factor - self.thickness/2, + 90) + self.fingerHolesAt(0, 0, self.lower_size) + with self.saved_context(): + self.moveTo( + r * self.rear_factor + self.thickness/2, + -r * self.lower_factor, + 0) + self.fingerHolesAt(0, 0, self.rear_size) + + if self.debug: + self.circle(0, 0, self.disc_diameter / 2) + + def _draw_slits(self, inset, halfslit): + total_x = 0 + + for x in self.sx: + center_x = total_x + x / 2 + + total_x += x + self.rectangularHole(inset, center_x, 2 * halfslit, self.disc_thickness) + if self.debug: + self.ctx.rectangle(inset - halfslit, center_x - x/2, 2 * halfslit, x) + + def lower_holes(self): + r = self.disc_diameter / 2 + inset = self.lower_outset + self.lower_halfslit + + self._draw_slits(inset, self.lower_halfslit) + + def rear_holes(self): + r = self.disc_diameter / 2 + inset = r * self.lower_factor + + self._draw_slits(inset, self.rear_halfslit) + + def render(self): + o = self.outer + + self.lower_factor = min(self.lower_factor, 0.99) + self.rear_factor = min(self.rear_factor, 0.99) + + self.rectangularWall(o, o, "eeee", move="right", callback=[self.sidewall_holes]) + self.rectangularWall(o, o, "eeee", move="right mirror", callback=[self.sidewall_holes]) + + self.rectangularWall(self.lower_size, sum(self.sx), "fffe", move="right", callback=[self.lower_holes]) + self.rectangularWall(self.rear_size, sum(self.sx), "fefh", move="right", callback=[self.rear_holes])
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/dispenser.html b/html/_modules/boxes/generators/dispenser.html new file mode 100644 index 0000000..fbb3895 --- /dev/null +++ b/html/_modules/boxes/generators/dispenser.html @@ -0,0 +1,206 @@ + + + + + + + + boxes.generators.dispenser — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.dispenser

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+class FrontEdge(edges.BaseEdge):
+    """An edge with room to get your fingers around cards"""
+    def __call__(self, length, **kw):
+        depth = self.settings.y * 2 / 3
+        t = self.settings.thickness
+        r = min(depth-t, length/4)
+        self.edge(length/4-t, tabs=2)
+        self.corner(90, t)
+        self.edge(depth-t-r, tabs=2)
+        self.corner(-90, r)
+        self.edge(length/2 - 2*r)
+        self.corner(-90, r)
+        self.edge(depth-t-r, tabs=2)
+        self.corner(90, t)
+        self.edge(length/4-t, tabs=2)
+
+
+
[docs]class Dispenser(Boxes): + """Dispenser for stackable (flat) items of same size""" + + description = """Set *bottomheight* to 0 for a wall mounting variant. +Please add mounting holes yourself.""" + + ui_group = "Misc" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings) + self.addSettingsArgs(edges.StackableSettings) + + self.buildArgParser(x=100, y=100, h=100) + + self.argparser.add_argument( + "--slotheight", action="store", type=float, default=10.0, + help="height of the dispenser slot / items (in mm)") + self.argparser.add_argument( + "--bottomheight", action="store", type=float, default=0.0, + help="height underneath the dispenser (in mm)") + self.argparser.add_argument( + "--sideedges", action="store", type=ArgparseEdgeType("Fh"), + choices=list("Fh"), default="F", + help="edges used for holding the front panels and back") + + + def render(self): + x, y, h, hs = self.x, self.y, self.h, self.slotheight + hb = self.bottomheight + t = self.thickness + + se = self.sideedges + fe = FrontEdge(self, self) + + hb = max(0, hb-self.edges["š"].spacing()) + th = h + (hb+t if hb else 0.0) + hh = hb + 0.5*t + + with self.saved_context(): + self.rectangularWall(x, y, [fe, "f", "f", "f"], + label="Floor", move="right") + self.rectangularWall(x, y, "eeee", label="Lid bottom", move="right") + self.rectangularWall(x, y, "EEEE", label="Lid top", move="right") + + + self.rectangularWall(x, y, "ffff", move="up only") + + if hb: + frontedge = edges.CompoundEdge(self, "Ef", (hb+t+hs, h-hs)) + self.rectangularWall( + y, th, ("š", frontedge, "e", "f"), ignore_widths=[6], + callback=[lambda:self.fingerHolesAt(0, hh, y, 0)], + label="Left wall", move="right mirror") + self.rectangularWall( + x, th, ["š", se, "e", se], ignore_widths=[1, 6], + callback=[lambda:self.fingerHolesAt(0, hh, x, 0)], + label="Back wall", move="right") + self.rectangularWall( + y, th, ("š", frontedge, "e", "f"), ignore_widths=[6], + callback=[lambda:self.fingerHolesAt(0, hh, y, 0)], + label="Right wall", move="right") + + else: + frontedge = edges.CompoundEdge(self, "Ef", (hs, h-hs)) + self.rectangularWall( + y, th, ("h", frontedge, "e", "f"), + label="Left wall", ignore_widths=[6], move="right mirror") + self.rectangularWall( + x, th, ["h", se, "e", se], ignore_widths=[1, 6], + label="Back wall", move="right") + self.rectangularWall( + y, th, ("h", frontedge, "e", "f"), + label="Right wall", ignore_widths=[6], move="right") + + self.rectangularWall(x/3, h-hs, "eee" + se, + label="Left front", move="right") + self.rectangularWall(x/3, h-hs, "eee" + se, + label="Right front", move="mirror right")
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/display.html b/html/_modules/boxes/generators/display.html new file mode 100644 index 0000000..650ed28 --- /dev/null +++ b/html/_modules/boxes/generators/display.html @@ -0,0 +1,144 @@ + + + + + + + + boxes.generators.display — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.display

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+import math
+
+
[docs]class Display(Boxes): + """Diplay for flyers or leaflets""" + + ui_group = "Misc" + + def __init__(self): + Boxes.__init__(self) + + self.buildArgParser(x=150., h=200.0) + # Add non default cli params if needed (see argparse std lib) + self.argparser.add_argument( + "--radius", action="store", type=float, default=5., + help="radius of the corners in mm") + self.argparser.add_argument( + "--angle", action="store", type=float, default=0., + help="greater zero for top wider as bottom") + + + def render(self): + # adjust to the variables you want in the local scope + x, h, r = self.x, self.h, self.radius + a = self.angle + t = self.thickness + + self.roundedPlate(0.7*x, x, r, "e", extend_corners=False, move="up") + + oh = 1.2*h-2*r + if a > 0: + self.moveTo(math.sin(math.radians(a))*oh) + self.rectangularHole(x/2, h*0.2, 0.7*x+0.1*t, 1.3*t) + self.moveTo(r) + self.polyline(x-2*r, (90-a, r), oh, (90+a, r), + x-2*r+2*math.sin(math.radians(a))*oh, + (90+a, r), oh, (90-a, r))
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/displaycase.html b/html/_modules/boxes/generators/displaycase.html new file mode 100644 index 0000000..77a5172 --- /dev/null +++ b/html/_modules/boxes/generators/displaycase.html @@ -0,0 +1,151 @@ + + + + + + + + boxes.generators.displaycase — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.displaycase

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+# Copyright (C) 2018 Alexander Bulimov
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
+
[docs]class DisplayCase(Boxes): + """Fully closed box intended to be cut from transparent acrylics and to serve as a display case.""" + + ui_group = "Box" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.buildArgParser("x", "y", "h", "outside") + self.argparser.add_argument( + "--overhang", + action="store", + type=float, + default=2, + help="overhang for joints in mm", + ) + + def render(self): + + x, y, h = self.x, self.y, self.h + + if self.outside: + x = self.adjustSize(x) + y = self.adjustSize(y) + h = self.adjustSize(h) + + t = self.thickness + + d2 = edges.Bolts(2) + d3 = edges.Bolts(3) + + d2 = d3 = None + + self.rectangularWall(x, h, "ffff", bedBolts=[d2] * 4, move="right", label="Wall 1") + self.rectangularWall(y, h, "fFfF", bedBolts=[d3, d2, d3, d2], move="up", label="Wall 2") + self.rectangularWall(y, h, "fFfF", bedBolts=[d3, d2, d3, d2], label="Wall 4") + self.rectangularWall(x, h, "ffff", bedBolts=[d2] * 4, move="left up", label="Wall 3") + + self.flangedWall(x, y, "FFFF", flanges=[self.overhang] * 4, move="right", label="Top") + self.flangedWall(x, y, "FFFF", flanges=[self.overhang] * 4, label="Bottom")
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/displayshelf.html b/html/_modules/boxes/generators/displayshelf.html new file mode 100644 index 0000000..d13f70a --- /dev/null +++ b/html/_modules/boxes/generators/displayshelf.html @@ -0,0 +1,174 @@ + + + + + + + + boxes.generators.displayshelf — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.displayshelf

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class DisplayShelf(Boxes): # change class name here and below + """Shelf with slanted floors""" + + ui_group = "Shelf" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings) + + self.buildArgParser(x=400, y=100, h=300, outside=True) + self.argparser.add_argument( + "--num", action="store", type=int, default=3, + help="number of shelves") + self.argparser.add_argument( + "--front", action="store", type=float, default=20.0, + help="height of front walls") + self.argparser.add_argument( + "--angle", action="store", type=float, default=30.0, + help="angle of floors (negative values for slanting backwards)") + + def side(self): + + t = self.thickness + a = math.radians(self.angle) + + hs = (self.sl+t) * math.sin(a) + math.cos(a) * t + + for i in range(self.num): + pos_x = abs(0.5*t*math.sin(a)) + pos_y = hs - math.cos(a)*0.5*t + i * (self.h-abs(hs)) / (self.num - 0.5) + if a < 0: + pos_y += -math.sin(a) * self.sl + + self.fingerHolesAt(pos_x, pos_y, self.sl, -self.angle) + pos_x += math.cos(-a) * (self.sl+0.5*t) + math.sin(a)*0.5*t + pos_y += math.sin(-a) * (self.sl+0.5*t) + math.cos(a)*0.5*t + self.fingerHolesAt(pos_x, pos_y, self.front, 90-self.angle) + + def render(self): + # adjust to the variables you want in the local scope + x, y, h = self.x, self.y, self.h + f = self.front + t = self.thickness + + if self.outside: + x = self.adjustSize(x) + + + a = math.radians(self.angle) + + self.sl = sl = (y - (t * (math.cos(a) + abs(math.sin(a)))) - max(0, math.sin(a) * f)) / math.cos(a) + + # render your parts here + self.rectangularWall(y, h, callback=[self.side], move="up") + self.rectangularWall(y, h, callback=[self.side], move="up") + + if f: + for i in range(self.num): + self.rectangularWall(x, sl, "ffef", move="up") + self.rectangularWall(x, f, "Ffef", move="up") + else: + for i in range(self.num): + self.rectangularWall(x, sl, "Efef", move="up")
+ + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/dividertray.html b/html/_modules/boxes/generators/dividertray.html new file mode 100644 index 0000000..4f922c9 --- /dev/null +++ b/html/_modules/boxes/generators/dividertray.html @@ -0,0 +1,752 @@ + + + + + + + + boxes.generators.dividertray — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.dividertray

+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from functools import partial
+from boxes import Boxes, edges, boolarg
+import math
+
+
+class NotchSettings(edges.Settings):
+    """Settings for Notches on the Dividers"""
+
+    absolute_params = {
+        "upper_radius": 1,
+        "lower_radius": 8,
+        "depth": 15,
+    }
+
+
+class SlotSettings(edges.Settings):
+    """Settings for Divider Slots
+
+    Values:
+
+    * absolute
+      * depth : 20 : depth of the slot in mm
+      * angle : 0 : angle at which slots are generated, in degrees. 0° is vertical.
+      * radius : 2 : radius of the slot entrance in mm
+      * extra_slack : 0.2 : extra slack (in addition to thickness and kerf) to help insert dividers in mm"""
+
+    absolute_params = {
+        "depth": 20,
+        "angle": 0,
+        "radius": 2,
+        "extra_slack": 0.2,
+    }
+
+
+class DividerSettings(edges.Settings):
+    """Settings for Dividers
+    Values:
+
+    * absolute_params
+
+     * bottom_margin : 0 : margin between box's bottom and divider's in mm
+
+    * relative (in multiples of thickness)
+
+     * play : 0.05 : play to avoid them clamping onto the walls (in multiples of thickness)
+    """
+
+    absolute_params = {
+        "bottom_margin": 0,
+    }
+    relative_params = {
+        "play": 0.05,
+    }
+
+
+
[docs]class DividerTray(Boxes): + """Divider tray - rows and dividers""" + + description = """ +Adding '0:' at the start of the sy parameter adds a slot at the very back. Adding ':0' at the end of sy adds a slot meeting the bottom at the very front. This is especially useful if slot angle is set above zero. + +There are 4 different sets of dividers rendered: + +* With asymetric tabs so the tabs fit on top of each other +* With tabs of half wall thickness that can go side by side +* With tabs of a full wall thickness +* One single divider spanning across all columns + +You will likely need to cut each of the dividers you want multiple times. +""" + + ui_group = "Tray" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.addSettingsArgs(edges.HandleEdgeSettings) + self.buildArgParser("sx", "sy", "h", "outside") + self.addSettingsArgs(SlotSettings) + self.addSettingsArgs(NotchSettings) + self.addSettingsArgs(DividerSettings) + self.argparser.add_argument( + "--notches_in_wall", + type=boolarg, + default=True, + help="generate the same notches on the walls that are on the dividers", + ) + self.argparser.add_argument( + "--left_wall", + type=boolarg, + default=True, + help="generate wall on the left side", + ) + self.argparser.add_argument( + "--right_wall", + type=boolarg, + default=True, + help="generate wall on the right side", + ) + self.argparser.add_argument( + "--bottom", type=boolarg, default=False, help="generate wall on the bottom", + ) + self.argparser.add_argument( + "--handle", type=boolarg, default=False, help="add handle to the bottom", + ) + + def render(self): + + side_walls_number = len(self.sx) - 1 + sum([self.left_wall, self.right_wall]) + if side_walls_number == 0: + raise ValueError("You need at least one side wall to generate this tray") + + # We need to adjust height before slot generation + if self.outside: + if self.bottom: + self.h -= self.thickness + else: + # If the parameter 'h' is the inner height of the content itself, + # then the actual tray height needs to be adjusted with the angle + self.h = self.h * math.cos(math.radians(self.Slot_angle)) + + slot_descriptions = SlotDescriptionsGenerator().generate_all_same_angles( + self.sy, + self.thickness, + self.Slot_extra_slack, + self.Slot_depth, + self.h, + self.Slot_angle, + self.Slot_radius, + ) + + # If measures are outside, we need to readjust slots afterwards + if self.outside: + self.sx = self.adjustSize(self.sx, self.left_wall, self.right_wall) + side_wall_target_length = sum(self.sy) - 2 * self.thickness + slot_descriptions.adjust_to_target_length(side_wall_target_length) + + self.ctx.save() + + # Facing walls (outer) with finger holes to support side walls + facing_wall_length = sum(self.sx) + self.thickness * (len(self.sx) - 1) + side_edge = lambda with_wall: "F" if with_wall else "e" + bottom_edge = lambda with_wall, with_handle: ("f" if with_handle else "F") if with_wall else "e" + upper_edge = ( + DividerNotchesEdge( + self, + list(reversed(self.sx)), + ) + if self.notches_in_wall + else "e" + ) + for _ in range(2): + self.rectangularWall( + facing_wall_length, + self.h, + [ + bottom_edge(self.bottom, _ and self.handle), + side_edge(self.right_wall), + upper_edge, + side_edge(self.left_wall), + ], + callback=[partial(self.generate_finger_holes, self.h)], + move="up", label = "Front" if _ else "Back", + ) + + # Side walls (outer & inner) with slots to support dividers + side_wall_length = slot_descriptions.total_length() + for _ in range(side_walls_number): + if _ < side_walls_number - (len(self.sx) - 1): + be = "F" if self.bottom else "e" + else: + be = "f" if self.bottom else "e" + se = DividerSlotsEdge(self, slot_descriptions.descriptions) + self.rectangularWall( + side_wall_length, self.h, [be, "f", se, "f"], move="up", label="Sidepiece " + str(_ + 1) + ) + + # Switch to right side of the file + self.ctx.restore() + self.rectangularWall( + max(facing_wall_length, side_wall_length), self.h, "ffff", move="right only", label="invisible" + ) + + # Bottom piece. + if self.bottom: + self.rectangularWall( + facing_wall_length, + side_wall_length, + [ + "f", + "f" if self.right_wall else "e", + "Y" if self.handle else "f", + "f" if self.left_wall else "e", + ], + callback=[partial(self.generate_finger_holes, side_wall_length)], + move="up", label="Bottom", + ) + + # Dividers + divider_height = ( + # h, with angle adjustement + self.h / math.cos(math.radians(self.Slot_angle)) + # removing what exceeds in the width of the divider + - self.thickness * math.tan(math.radians(self.Slot_angle)) + # with margin + - self.Divider_bottom_margin + ) + self.generate_divider( + self.sx, divider_height, "up", + first_tab_width=self.thickness if self.left_wall else 0, + second_tab_width=self.thickness if self.right_wall else 0 + ) + for tabs, asymetric_tabs in [(self.thickness, None), + (self.thickness / 2, None), + (self.thickness, 0.5),]: + with self.saved_context(): + for i, length in enumerate(self.sx): + self.generate_divider( + [length], + divider_height, + "right", + first_tab_width=tabs if self.left_wall or i>0 else 0, + second_tab_width=tabs if self.right_wall or i<(len(self.sx) - 1) else 0, + asymetric_tabs=asymetric_tabs, + ) + if asymetric_tabs: + self.moveTo(-tabs, self.spacing) + self.generate_divider(self.sx, divider_height, "up only") + + if self.debug: + debug_info = ["Debug"] + debug_info.append( + "Slot_edge_outer_length:{0:.2f}".format( + slot_descriptions.total_length() + 2 * self.thickness + ) + ) + debug_info.append( + "Slot_edge_inner_lengths:{0}".format( + str.join( + "|", + [ + "{0:.2f}".format(e.usefull_length()) + for e in slot_descriptions.get_straigth_edges() + ], + ) + ) + ) + debug_info.append( + "Face_edge_outer_length:{0:.2f}".format( + facing_wall_length + + self.thickness * sum([self.left_wall, self.right_wall]) + ) + ) + debug_info.append( + "Face_edge_inner_lengths:{0}".format( + str.join("|", ["{0:.2f}".format(e) for e in self.sx]) + ) + ) + debug_info.append("Tray_height:{0:.2f}".format(self.h)) + debug_info.append( + "Content_height:{0:.2f}".format( + self.h / math.cos(math.radians(self.Slot_angle)) + ) + ) + self.text(str.join("\n", debug_info), x=5, y=5, align="bottom left") + + def generate_finger_holes(self, length): + posx = -0.5 * self.thickness + for x in self.sx[:-1]: + posx += x + self.thickness + self.fingerHolesAt(posx, 0, length) + + def generate_divider( + self, widths, height, move, + first_tab_width=0, second_tab_width=0, + asymetric_tabs=None): + total_width = sum(widths) + (len(widths)-1) * self.thickness + first_tab_width + second_tab_width + + if self.move(total_width, height, move, True): + return + + play = self.Divider_play + left_tab_height = right_tab_height = self.Slot_depth + if asymetric_tabs: + left_tab_height = left_tab_height * asymetric_tabs - play + right_tab_height = right_tab_height * (1-asymetric_tabs) + + # Upper: first tab width + if asymetric_tabs: + self.moveTo(first_tab_width - play) + else: + self.edge(first_tab_width - play) + # Upper edge with a finger notch + for nr, width in enumerate(widths): + if nr > 0: + self.edge(self.thickness) + DividerNotchesEdge( + self, + [width], + )(width) + + self.polyline( + # Upper: second tab width if needed + second_tab_width - play, + # First side, with tab depth only if there is 2 walls + 90, + left_tab_height, + 90, + second_tab_width, + -90, + height - left_tab_height, + 90, + ) + # Lower edge + for width in reversed(widths[1:]): + self.polyline( + width - 2 * play, + 90, + height - self.Slot_depth, + -90, + self.thickness + 2 * play, + -90, + height - self.Slot_depth, + 90, + ) + + self.polyline( + # Second side tab + widths[0] - 2 * play, + 90, + height - self.Slot_depth, + -90, + first_tab_width, + 90, + right_tab_height, + 90 + ) + if asymetric_tabs: + self.polyline( + first_tab_width - play, + -90, + self.Slot_depth-right_tab_height, + 90 + ) + + # Move for next piece + self.move(total_width, height, move, label="Divider")
+ + +class SlottedEdgeDescriptions: + def __init__(self): + self.descriptions = [] + + def add(self, description): + self.descriptions.append(description) + + def get_straigth_edges(self): + return [x for x in self.descriptions if isinstance(x, StraightEdgeDescription)] + + def get_last_edge(self): + return self.descriptions[-1] + + def adjust_to_target_length(self, target_length): + actual_length = sum([d.tracing_length() for d in self.descriptions]) + compensation = actual_length - target_length + + compensation_ratio = compensation / sum( + [d.asked_length for d in self.get_straigth_edges()] + ) + + for edge in self.get_straigth_edges(): + edge.outside_ratio = 1 - compensation_ratio + + def total_length(self): + return sum([x.tracing_length() for x in self.descriptions]) + + +class StraightEdgeDescription: + def __init__( + self, + asked_length, + round_edge_compensation=0, + outside_ratio=1, + angle_compensation=0, + ): + self.asked_length = asked_length + self.round_edge_compensation = round_edge_compensation + self.outside_ratio = outside_ratio + self.angle_compensation = angle_compensation + + def __repr__(self): + return ( + "StraightEdgeDescription({0}, round_edge_compensation={1}, angle_compensation={2}, outside_ratio={3})" + ).format( + self.asked_length, + self.round_edge_compensation, + self.angle_compensation, + self.outside_ratio, + ) + + def tracing_length(self): + """ + How much length should take tracing this straight edge + """ + return ( + (self.asked_length * self.outside_ratio) + - self.round_edge_compensation + + self.angle_compensation + ) + + def usefull_length(self): + """ + Part of the length which might be used by the content of the tray + """ + return self.asked_length * self.outside_ratio + + +class Memoizer(dict): + def __init__(self, computation): + self.computation = computation + + def __missing__(self, key): + res = self[key] = self.computation(key) + return res + + +class SlotDescription: + _div_by_cos_cache = Memoizer(lambda a: 1 / math.cos(math.radians(a))) + _tan_cache = Memoizer(lambda a: math.tan(math.radians(a))) + + def __init__( + self, width, depth=20, angle=0, radius=0, start_radius=None, end_radius=None + ): + self.depth = depth + self.width = width + self.start_radius = radius if start_radius == None else start_radius + self.end_radius = radius if end_radius == None else end_radius + self.angle = angle + + def __repr__(self): + return "SlotDescription({0}, depth={1}, angle={2}, start_radius={3}, end_radius={4})".format( + self.width, self.depth, self.angle, self.start_radius, self.end_radius + ) + + def _div_by_cos(self): + return SlotDescription._div_by_cos_cache[self.angle] + + def _tan(self): + return SlotDescription._tan_cache[self.angle] + + def angle_corrected_width(self): + """ + returns how much width is the slot when measured horizontally, since the angle makes it bigger. + It's the same as the slot entrance width when radius is 0°. + """ + return self.width * self._div_by_cos() + + def round_edge_start_correction(self): + """ + returns by how much we need to stop tracing our straight lines at the start of the slot + in order to do a curve line instead + """ + return self.start_radius * (self._div_by_cos() - self._tan()) + + def round_edge_end_correction(self): + """ + returns by how much we need to stop tracing our straight lines at the end of the slot + in order to do a curve line instead + """ + return self.end_radius * (self._div_by_cos() + self._tan()) + + def _depth_angle_correction(self): + """ + The angle makes one side of the slot deeper than the other. + """ + extra_depth = self.width * self._tan() + return extra_depth + + def corrected_start_depth(self): + """ + Returns the depth of the straigth part of the slot starting side + """ + extra_depth = self._depth_angle_correction() + return self.depth + max(0, extra_depth) - self.round_edge_start_correction() + + def corrected_end_depth(self): + """ + Returns the depth of the straigth part of the slot ending side + """ + extra_depth = self._depth_angle_correction() + return self.depth + max(0, -extra_depth) - self.round_edge_end_correction() + + def tracing_length(self): + """ + How much length this slot takes on an edge + """ + return ( + self.round_edge_start_correction() + + self.angle_corrected_width() + + self.round_edge_end_correction() + ) + + +class SlotDescriptionsGenerator: + def generate_all_same_angles( + self, sections, thickness, extra_slack, depth, height, angle, radius=2, + ): + width = thickness + extra_slack + + descriptions = SlottedEdgeDescriptions() + + # Special case: if first slot start at 0, then radius is 0 + first_correction = 0 + current_section = 0 + if sections[0] == 0: + slot = SlotDescription( + width, depth=depth, angle=angle, start_radius=0, end_radius=radius, + ) + descriptions.add(slot) + first_correction = slot.round_edge_end_correction() + current_section += 1 + + first_length = sections[current_section] + current_section += 1 + descriptions.add( + StraightEdgeDescription( + first_length, round_edge_compensation=first_correction + ) + ) + + for l in sections[current_section:]: + slot = SlotDescription(width, depth=depth, angle=angle, radius=radius,) + + # Fix previous edge length + previous_edge = descriptions.get_last_edge() + previous_edge.round_edge_compensation += slot.round_edge_start_correction() + + # Add this slot + descriptions.add(slot) + + # Add the straigth edge after this slot + descriptions.add( + StraightEdgeDescription(l, slot.round_edge_end_correction()) + ) + + # We need to add extra space for the divider (or the actual content) + # to slide all the way down to the bottom of the tray in spite of walls + end_length = height * math.tan(math.radians(angle)) + descriptions.get_last_edge().angle_compensation += end_length + + return descriptions + + +class DividerNotchesEdge(edges.BaseEdge): + """Edge with multiple notches for easier access to dividers""" + + description = "Edge with multiple notches for easier access to dividers" + + def __init__(self, boxes, sx): + + super().__init__(boxes, None) + + self.sx = sx + + def __call__(self, _, **kw): + first = True + for width in self.sx: + if first: + first = False + else: + self.edge(self.thickness) + self.edge_with_notch(width) + + def edge_with_notch(self, width): + # width (with notch if possible) + upper_third = ( + width - 2 * self.Notch_upper_radius - 2 * self.Notch_lower_radius + ) / 3 + if upper_third > 0: + straightHeight = ( + self.Notch_depth - self.Notch_upper_radius - self.Notch_lower_radius + ) + self.polyline( + upper_third, + (90, self.Notch_upper_radius), + straightHeight, + (-90, self.Notch_lower_radius), + upper_third, + (-90, self.Notch_lower_radius), + straightHeight, + (90, self.Notch_upper_radius), + upper_third, + ) + else: + # if there isn't enough room for the radius, we don't use it + self.edge(width) + + +class DividerSlotsEdge(edges.BaseEdge): + """Edge with multiple angled rounded slots for dividers""" + + description = "Edge with multiple angled rounded slots for dividers" + + def __init__(self, boxes, descriptions): + + super().__init__(boxes, None) + + self.descriptions = descriptions + + def __call__(self, length, **kw): + + self.ctx.save() + + for description in self.descriptions: + if isinstance(description, SlotDescription): + self.do_slot(description) + elif isinstance(description, StraightEdgeDescription): + self.do_straight_edge(description) + + # rounding errors might accumulates : + # restore context and redo the move straight + self.ctx.restore() + self.moveTo(length) + + def do_straight_edge(self, straight_edge): + self.edge(straight_edge.tracing_length()) + + def do_slot(self, slot): + self.ctx.save() + + self.polyline( + 0, + (90 - slot.angle, slot.start_radius), + slot.corrected_start_depth(), + -90, + slot.width, + -90, + slot.corrected_end_depth(), + (90 + slot.angle, slot.end_radius), + ) + + # rounding errors might accumulates : + # restore context and redo the move straight + self.ctx.restore() + self.moveTo(slot.tracing_length()) +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/doubleflexdoorbox.html b/html/_modules/boxes/generators/doubleflexdoorbox.html new file mode 100644 index 0000000..bb9741a --- /dev/null +++ b/html/_modules/boxes/generators/doubleflexdoorbox.html @@ -0,0 +1,218 @@ + + + + + + + + boxes.generators.doubleflexdoorbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.doubleflexdoorbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import boxes
+import math
+
+
+
[docs]class DoubleFlexDoorBox(boxes.Boxes): + """Box with two part lid with living hinges and round corners""" + + ui_group = "FlexBox" + + def __init__(self): + boxes.Boxes.__init__(self) + self.addSettingsArgs(boxes.edges.FingerJointSettings) + self.addSettingsArgs(boxes.edges.FlexSettings) + self.buildArgParser("x", "y", "h", "outside") + self.argparser.add_argument( + "--radius", action="store", type=float, default=15, + help="Radius of the latch in mm") + self.argparser.add_argument( + "--latchsize", action="store", type=float, default=8, + help="size of latch in multiples of thickness") + + def flexBoxSide(self, x, y, r, callback=None, move=None): + t = self.thickness + ll = (x - 2*r) / 2 - self.latchsize + + if self.move(x+2*t, y+2*t, move, True): + return + + self.moveTo(t+r, t) + + for i, l in zip(range(2), (x, y)): + self.cc(callback, i) + self.edges["f"](l - 2 * r) + self.corner(90, r) + + self.cc(callback, 2) + self.edge(ll) + self.latch(self.latchsize) + self.latch(self.latchsize, reverse=True) + self.edge(ll) + + self.corner(90, r) + self.cc(callback, 3) + self.edges["f"](y - 2 * r) + self.corner(90, r) + + self.move(x+2*t, y+2*t, move) + + def surroundingWall(self, x, y, h, r, move=None): + t = self.thickness + c4 = math.pi * r * 0.5 + + tw = 2*x + 2*y - 8*r + 4*c4 + th = h + 2.5*t + + if self.move(tw, th, move, True): + return + + self.moveTo(0, 0.25*t, -90) + + self.latch(self.latchsize, False, True) + self.edge((x-2*r)/2 - self.latchsize, False) + if y - 2 * r < t: + self.edges["X"](2 * c4 + y - 2 * r, h + 2 * t) + else: + self.edges["X"](c4, h + 2 * t) + self.edges["F"](y - 2 * r, False) + self.edges["X"](c4, h + 2 * t) + self.edges["F"](x - 2 * r, False) + if y - 2 * r < t: + self.edges["X"](2 * c4 + y - 2 * r, h + 2 * t) + else: + self.edges["X"](c4, h + 2 * t) + self.edges["F"](y - 2 * r) + self.edges["X"](c4, h + 2 * t) + self.edge((x-2*r)/2 - self.latchsize, False) + self.latch(self.latchsize, False) + self.edge(h + 2 * t) + self.latch(self.latchsize, False, True) + self.edge((x-2*r)/2 - self.latchsize, False) + self.edge(c4) + self.edges["F"](y - 2 * r) + self.edge(c4) + self.edges["F"](x - 2 * r, False) + self.edge(c4) + self.edges["F"](y - 2 * r, False) + self.edge(c4) + self.edge((x-2*r)/2 - self.latchsize) + self.latch(self.latchsize, False, False) + self.edge(h + 2 * t) + + self.move(tw, th, move) + + def render(self): + + if self.outside: + self.x = self.adjustSize(self.x) + self.y = self.adjustSize(self.y) + self.h = self.adjustSize(self.h) + + t = self.thickness + self.latchsize *= t + x, y, h = self.x, self.y, self.h + r = self.radius or min(x - 2*self.latchsize, y) / 2.0 + r = min(r, y / 2.0) + self.radius = r = min(r, max(0, (x - 2*self.latchsize) / 2.0)) + + + # swap y and h for more consistent axis names + self.surroundingWall(x, h, y, r, move="up") + self.flexBoxSide(x, h, r, move="right") + self.flexBoxSide(x, h, r, move="mirror")
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/drillbox.html b/html/_modules/boxes/generators/drillbox.html new file mode 100644 index 0000000..e26a6d5 --- /dev/null +++ b/html/_modules/boxes/generators/drillbox.html @@ -0,0 +1,215 @@ + + + + + + + + boxes.generators.drillbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.drillbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import Boxes, edges, Color, ArgparseEdgeType
+from boxes.lids import _TopEdge
+
+
[docs]class DrillBox(_TopEdge): + """A parametrized box for drills""" + + ui_group = "Tray" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings, + space=3, finger=3, surroundingspaces=1) + self.addSettingsArgs(edges.RoundedTriangleEdgeSettings, outset=1) + self.addSettingsArgs(edges.StackableSettings) + self.addSettingsArgs(edges.MountingSettings) + self.argparser.add_argument( + "--top_edge", action="store", + type=ArgparseEdgeType("eStG"), choices=list("eStG"), + default="e", help="edge type for top edge") + self.buildArgParser(sx="25*3", sy="60*4", sh="5:25:10", + bottom_edge="h") + self.argparser.add_argument( + "--holes", + action="store", + type=int, + default=3, + help="Number of holes for each size", + ) + self.argparser.add_argument( + "--firsthole", + action="store", + type=float, + default=1.0, + help="Smallest hole", + ) + self.argparser.add_argument( + "--holeincrement", + action="store", + type=float, + default=.5, + help="increment between holes", + ) + + def sideholes(self, l): + t = self.thickness + h = -0.5 * t + for d in self.sh[:-1]: + h += d + t + self.fingerHolesAt(0, h, l, angle=0) + + + def drillholes(self, description=False): + y = 0 + d = self.firsthole + for dy in self.sy: + x = 0 + for dx in self.sx: + iy = dy / self.holes + for k in range(self.holes): + self.hole(x + dx / 2, y + (k + 0.5) * iy, d=d + 0.05) + if description: + self.rectangularHole(x + dx / 2, y + dy / 2, dx - 2, dy - 2, color=Color.ETCHING) + self.text( + "%.1f" % d, + x + 2, + y + 2, + 270, + align="right", + fontsize=6, + color=Color.ETCHING, + ) + # TODO: make the fontsize dynamic to make the text fit in all cases + d += self.holeincrement + x += dx + y += dy + + def render(self): + x = sum(self.sx) + y = sum(self.sy) + + h = sum(self.sh) + self.thickness * (len(self.sh)-1) + b = self.bottom_edge + t1, t2, t3, t4 = self.topEdges(self.top_edge) + + self.rectangularWall( + x, h, [b, "f", t1, "F"], + ignore_widths=[1, 6], + callback=[lambda: self.sideholes(x)], move="right") + self.rectangularWall( + y, h, [b, "f", t2, "F"], callback=[lambda: self.sideholes(y)], + ignore_widths=[1, 6], + move="up") + self.rectangularWall( + y, h, [b, "f", t3, "F"], callback=[lambda: self.sideholes(y)], + ignore_widths=[1, 6]) + self.rectangularWall( + x, h, [b, "f", t4, "F"], + ignore_widths=[1, 6], + callback=[lambda: self.sideholes(x)], move="left up") + if b != "e": + self.rectangularWall(x, y, "ffff", move="right") + for d in self.sh[:-2]: + self.rectangularWall( + x, y, "ffff", callback=[self.drillholes], move="right") + self.rectangularWall( + x, y, "ffff", + callback=[lambda: self.drillholes(description=True)], + move="right")
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/drillstand.html b/html/_modules/boxes/generators/drillstand.html new file mode 100644 index 0000000..0e92671 --- /dev/null +++ b/html/_modules/boxes/generators/drillstand.html @@ -0,0 +1,324 @@ + + + + + + + + boxes.generators.drillstand — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.drillstand

+#!/usr/bin/env python3
+# Copyright (C) 2013-2019 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+import boxes
+
+
[docs]class DrillStand(Boxes): + """Box for drills with each compartment of a different height""" + + description = """Note: `sh` gives the hight of the rows front to back. It though should have the same number of entries as `sy`. These heights are the one on the left side and increase throughout the row. To have each compartement a bit higher than the previous one the steps in `sh` should be a bit bigger than `extra_height`. + +Assembly: + +![Parts](static/samples/DrillStand-drawing.png) + +Start with putting the slots of the inner walls together. Be especially careful with adding the bottom. It is always assymetrical and flush with the right/lower side while being a little short on the left/higher side to not protrude into the side wall. + +| | | +| ---- | ---- | +| ![Assembly inner walls](static/samples/DrillStand-assembly-1.jpg) | ![Assembly bottom](static/samples/DrillStand-assembly-2.jpg) | +| Then add the front and the back wall. | Add the very left and right walls last. | +| ![Assembly front and back](static/samples/DrillStand-assembly-3.jpg) | ![Assembly side walls](static/samples/DrillStand-assembly-4.jpg) | +""" + + ui_group = "Misc" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.StackableSettings, height=1.0, width=3) + self.addSettingsArgs(edges.FingerJointSettings) + + self.buildArgParser(sx="25*6", sy="10:20:30", sh="25:40:60") + self.argparser.add_argument( + "--extra_height", action="store", type=float, default=15.0, + help="height difference left to right") + + def yWall(self, nr, move=None): + t = self.thickness + x, sx, y, sy, sh = self.x, self.sx, self.y, self.sy, self.sh + eh = self.extra_height * (sum(sx[:nr])+ nr*t - t)/x + + tw, th = sum(sy) + t * len(sy) + t, max(sh) + eh + + if self.move(tw, th, move, True): + return + + self.moveTo(t) + self.polyline(y, 90) + self.edges["f"](sh[-1]+eh) + self.corner(90) + for i in range(len(sy)-1, 0, -1): + s1 = max(sh[i]-sh[i-1], 0) + 4*t + s2 = max(sh[i-1]-sh[i], 0) + 4*t + + self.polyline(sy[i], 90, s1, -90, t, -90, s2, 90) + self.polyline(sy[0], 90) + self.edges["f"](sh[0] + eh) + self.corner(90) + + self.move(tw, th, move) + + def sideWall(self, extra_height=0.0, foot_height=0.0, edges="šFf", move=None): + t = self.thickness + x, sx, y, sy, sh = self.x, self.sx, self.y, self.sy, self.sh + eh = extra_height + fh = foot_height + + edges = [self.edges.get(e, e) for e in edges] + + tw = sum(sy) + t * len(sy) + t + th = max(sh) + eh + fh + edges[0].spacing() + + if self.move(tw, th, move, True): + return + + self.moveTo(edges[0].margin()) + + edges[0](y+2*t) + self.edgeCorner(edges[0], "e") + self.edge(fh) + self.step(edges[1].startwidth() - t) + edges[1](sh[-1]+eh) + self.edgeCorner(edges[1], "e") + for i in range(len(sy)-1, 0, -1): + self.edge(sy[i]) + if sh[i] > sh[i-1]: + self.fingerHolesAt(0.5*t, self.burn, sh[i]+eh, 90) + self.polyline(t, 90, sh[i] - sh[i-1], -90) + else: + self.polyline(0, -90, sh[i-1] - sh[i], 90, t) + self.fingerHolesAt(-0.5*t, self.burn, sh[i-1]+eh) + self.polyline(sy[0]) + self.edgeCorner("e", edges[2]) + edges[2](sh[0]+eh) + self.step(t - edges[2].endwidth()) + self.polyline(fh) + self.edgeCorner("e", edges[0]) + + self.move(tw, th, move) + + def xWall(self, nr, move=None): + t = self.thickness + x, sx, y, sy, sh = self.x, self.sx, self.y, self.sy, self.sh + eh = self.extra_height + + tw, th = x + 2*t, sh[nr] + eh + t + + a = math.degrees(math.atan(eh / x)) + fa = 1 / math.cos(math.radians(a)) + + if self.move(tw, th, move, True): + return + + + self.moveTo(t, eh+t, -a) + + for i in range(len(sx)-1): + self.edges["f"](fa*sx[i]) + h = min(sh[nr - 1], sh[nr]) + s1 = h - 3.95*t + self.extra_height * (sum(sx[:i+1]) + i*t)/x + s2 = h - 3.95*t + self.extra_height * (sum(sx[:i+1]) + i*t + t)/x + + self.polyline(0, 90+a, s1, -90, t, -90, s2, 90-a) + self.edges["f"](fa*sx[-1]) + self.polyline(0, 90+a) + self.edges["f"](sh[nr]+eh) + self.polyline(0, 90, x, 90) + self.edges["f"](sh[nr]) + self.polyline(0, 90+a) + + self.move(tw, th, move) + + def xOutsideWall(self, h, edges="fFeF", move=None): + t = self.thickness + x, sx, y, sy, sh = self.x, self.sx, self.y, self.sy, self.sh + + edges = [self.edges.get(e, e) for e in edges] + eh = self.extra_height + + tw = x + edges[1].spacing() + edges[3].spacing() + th = h + eh + edges[0].spacing() + edges[2].spacing() + + a = math.degrees(math.atan(eh / x)) + fa = 1 / math.cos(math.radians(a)) + + if self.move(tw, th, move, True): + return + + + self.moveTo(edges[3].spacing(), eh+edges[0].margin(), -a) + + self.edge(t*math.tan(math.radians(a))) + if isinstance(edges[0], boxes.edges.FingerHoleEdge): + with self.saved_context(): + self.moveTo(0, 0, a) + self.fingerHolesAt( + 0, 1.5*t, x*fa - t*math.tan(math.radians(a)), -a) + self.edge(x*fa - t*math.tan(math.radians(a))) + elif isinstance(edges[0], boxes.edges.FingerJointEdge): + edges[0](x*fa - t*math.tan(math.radians(a))) + else: + raise ValueError("Only edges h and f supported: ") + self.corner(a) + self.edgeCorner(edges[0], "e", 90) + self.corner(-90) + self.edgeCorner("e", edges[1], 90) + edges[1](eh+h) + self.edgeCorner(edges[1], edges[2], 90) + edges[2](x) + self.edgeCorner(edges[2], edges[3], 90) + edges[3](h) + self.edgeCorner(edges[3], "e", 90) + self.corner(-90) + self.edgeCorner("e", edges[0], 90) + + self.moveTo(0, self.burn+edges[0].startwidth(), 0) + + for i in range(1, len(sx)): + posx = sum(sx[:i]) + i*t - 0.5 * t + length = h + self.extra_height * (sum(sx[:i]) + i*t - t)/x + self.fingerHolesAt(posx, h, length, -90) + + self.move(tw, th, move) + + def bottomCB(self): + t = self.thickness + x, sx, y, sy, sh = self.x, self.sx, self.y, self.sy, self.sh + eh = self.extra_height + + a = math.degrees(math.atan(eh / x)) + fa = 1 / math.cos(math.radians(a)) + + posy = -0.5 * t + for i in range(len(sy)-1): + posy += sy[i] + t + posx = -t * math.tan(math.radians(a)) # left side is clipped + for j in range(len(sx)): + self.fingerHolesAt(posx, posy, fa*sx[j], 0) + posx += fa*sx[j] + fa*t + + def render(self): + t = self.thickness + sx, sy, sh = self.sx, self.sy, self.sh + self.x = x = sum(sx) + len(sx)*t - t + self.y = y = sum(sy) + len(sy)*t - t + + bottom_angle = math.atan(self.extra_height / x) # radians + + self.xOutsideWall(sh[0], "hFeF", move="up") + for i in range(1, len(sy)): + self.xWall(i, move="up") + self.xOutsideWall(sh[-1], "hfef", move="up") + + self.rectangularWall(x/math.cos(bottom_angle)-t*math.tan(bottom_angle), y, "fefe", callback=[self.bottomCB], move="up") + + self.sideWall(foot_height=self.extra_height+2*t, move="right") + for i in range(1, len(sx)): + self.yWall(i, move="right") + self.sideWall(self.extra_height, 2*t, move="right")
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/electronicsbox.html b/html/_modules/boxes/generators/electronicsbox.html new file mode 100644 index 0000000..9c64125 --- /dev/null +++ b/html/_modules/boxes/generators/electronicsbox.html @@ -0,0 +1,189 @@ + + + + + + + + boxes.generators.electronicsbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.electronicsbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2017 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
+
[docs]class ElectronicsBox(Boxes): + """Closed box with screw on top and mounting holes""" + + ui_group = "Box" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.buildArgParser("x", "y", "h", "outside") + self.argparser.add_argument( + "--triangle", action="store", type=float, default=25., + help="Sides of the triangles holding the lid in mm") + self.argparser.add_argument( + "--d1", action="store", type=float, default=2., + help="Diameter of the inner lid screw holes in mm") + self.argparser.add_argument( + "--d2", action="store", type=float, default=3., + help="Diameter of the lid screw holes in mm") + self.argparser.add_argument( + "--d3", action="store", type=float, default=3., + help="Diameter of the mounting screw holes in mm") + self.argparser.add_argument( + "--outsidemounts", action="store", type=boolarg, default=True, + help="Add external mounting points") + self.argparser.add_argument( + "--holedist", action="store", type=float, default=7., + help="Distance of the screw holes from the wall in mm") + + def wallxCB(self): + t = self.thickness + self.fingerHolesAt(0, self.h-1.5*t, self.triangle, 0) + self.fingerHolesAt(self.x, self.h-1.5*t, self.triangle, 180) + + def wallyCB(self): + t = self.thickness + self.fingerHolesAt(0, self.h-1.5*t, self.triangle, 0) + self.fingerHolesAt(self.y, self.h-1.5*t, self.triangle, 180) + + def render(self): + + t = self.thickness + self.h = h = self.h + 2*t # compensate for lid + x, y, h = self.x, self.y, self.h + d1, d2, d3 =self.d1, self.d2, self.d3 + hd = self.holedist + tr = self.triangle + trh = tr / 3. + + if self.outside: + self.x = x = self.adjustSize(x) + self.y = y = self.adjustSize(y) + self.h = h = h - 3*t + + self.rectangularWall(x, h, "fFeF", callback=[self.wallxCB], + move="right", label="Wall 1") + self.rectangularWall(y, h, "ffef", callback=[self.wallyCB], + move="up", label="Wall 2") + self.rectangularWall(y, h, "ffef", callback=[self.wallyCB], + label="Wall 4") + self.rectangularWall(x, h, "fFeF", callback=[self.wallxCB], + move="left up", label="Wall 3") + + if not self.outsidemounts: + self.rectangularWall(x, y, "FFFF", callback=[ + lambda:self.hole(hd, hd, d=d3)] *4, move="right", + label="Bottom") + else: + self.flangedWall(x, y, edges="FFFF", + flanges=[0.0, 2*hd, 0., 2*hd], r=hd, + callback=[ + lambda:self.hole(hd, hd, d=d3)] * 4, move='up', + label="Bottom") + self.rectangularWall(x, y, callback=[ + lambda:self.hole(trh, trh, d=d2)] * 4, move='up', label="Top") + + self.rectangularTriangle(tr, tr, "ffe", num=4, + callback=[None, lambda: self.hole(trh, trh, d=d1)])
+ + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/eurorackskiff.html b/html/_modules/boxes/generators/eurorackskiff.html new file mode 100644 index 0000000..800877f --- /dev/null +++ b/html/_modules/boxes/generators/eurorackskiff.html @@ -0,0 +1,155 @@ + + + + + + + + boxes.generators.eurorackskiff — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.eurorackskiff

+#!/usr/bin/env python3
+# Copyright (C) 2013-2017 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
+
[docs]class EuroRackSkiff(Boxes): + """3U Height case with adjustable width and height and included rails""" + + ui_group = "Box" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.buildArgParser("h") + self.argparser.add_argument( + "--hp", action="store", type=int, default=84, + help="Width of the case in HP") + + + def wallxCB(self, x): + t = self.thickness + + def wallyCB(self, y): + t = self.thickness + self.fingerHolesAt(6, self.h-1.5*t, y, 0) + + def railHoles(self): + for i in range(0, self.hp): + self.hole(i*5.08 + 2.54, 3, d=3.0) + + def render(self): + + t = self.thickness + h = self.h + y = self.hp * 5.08 + x = 128.5 + + + self.rectangularWall(y, 6, "feee", callback=[self.railHoles] , move="up") + self.rectangularWall(y, 6, "feee", callback=[self.railHoles] , move="up") + self.rectangularWall(x, h, "fFeF", callback=[self.wallxCB(x)], + move="right") + self.rectangularWall(y, h, "ffef", callback=[self.wallyCB(y)], move="up") + self.rectangularWall(y, h, "ffef", callback=[self.wallyCB(y)]) + self.rectangularWall(x, h, "fFeF", callback=[self.wallxCB(x)], + move="left up") + self.rectangularWall(x, y, "FFFF", callback=[], move="right")
+ + + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/fanhole.html b/html/_modules/boxes/generators/fanhole.html new file mode 100644 index 0000000..6f881c2 --- /dev/null +++ b/html/_modules/boxes/generators/fanhole.html @@ -0,0 +1,197 @@ + + + + + + + + boxes.generators.fanhole — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.fanhole

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class FanHole(Boxes): + """Hole pattern for mounting a fan""" + + ui_group = "Holes" + + def __init__(self): + Boxes.__init__(self) + + self.argparser.add_argument( + "--diameter", action="store", type=float, default=80, + help="diameter of the fan hole") + self.argparser.add_argument( + "--mounting_holes", action="store", type=float, default=3, + help="diameter of the fan mounting holes") + self.argparser.add_argument( + "--mounting_holes_inset", action="store", type=float, default=5, + help="distance of the fan mounting holes from the outside") + self.argparser.add_argument( + "--arms", action="store", type=int, default=10, + help="number of arms") + self.argparser.add_argument( + "--inner_disc", action="store", type=float, default=.2, + help="relative size of the inner disc") + self.argparser.add_argument( + "--style", action="store", type=str, default="CW Swirl", + choices=["CW Swirl", "CCW Swirl", "Hole"], + help="Style of the fan hole") + + + def arc(self, d, a): + r = abs(1/math.cos(math.radians(90-a/2))*d/2) + self.corner(-a/2) + self.corner(a, r) + self.corner(-a/2) + + def swirl(self, r, ri_rel=.1, n=20): + + d = 2*r + #r = d/2 + ri = ri_rel * r + + ai = 90 + ao = 360/n * 0.8 + # angle going in + a1 = math.degrees(math.atan( + ri*math.sin(math.radians(ai)) / + (r - ri*math.cos(math.radians(ai))))) + d1= (ri*math.sin(math.radians(ai))**2 + + (r - ri*math.cos(math.radians(ai)))**2)**.5 + d2= (ri*math.sin(math.radians(ai-ao))**2 + + (r - ri*math.cos(math.radians(ai-ao)))**2)**.5 + + # angle coming out + a_i2 = math.degrees(math.atan( + (r*math.sin(math.radians(ao)) - ri*math.sin(math.radians(ai))) / + (r*math.cos(math.radians(ao)) - ri*math.cos(math.radians(ai))))) + a3 = a1 + a_i2 + a2 = 90 + a_i2 - ao + + self.moveTo(0, -r, 180) + + for i in range(n): + with self.saved_context(): + self.corner(-ao, r) + self.corner(-a2) + self.arc(d2, -90) + self.corner(-180+a3) + self.arc(d1, 85) + self.corner(-90-a1) + + self.moveArc(-360./n, r) + + def render(self): + r_h = self.mounting_holes / 2 + d = self.diameter + inset = self.mounting_holes_inset + + for px in (inset, d-inset): + for py in (inset, d-inset): + self.hole(px, py, r_h) + self.moveTo(d/2, d/2) + print(self.style) + if self.style == "CW Swirl": + self.ctx.scale(-1, 1) + self.swirl(d/2, self.inner_disc, self.arms) + elif self.style == "CCW Swirl": + self.swirl(d/2, self.inner_disc, self.arms) + else: #Hole + self.hole(0, 0, d=d)
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/filltest.html b/html/_modules/boxes/generators/filltest.html new file mode 100644 index 0000000..baf8b42 --- /dev/null +++ b/html/_modules/boxes/generators/filltest.html @@ -0,0 +1,168 @@ + + + + + + + + boxes.generators.filltest — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.filltest

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from shapely.geometry import *
+import random
+import time
+
+
[docs]class FillTest(Boxes): # Change class name! + """Piece for testing different settings for hole filling""" + + ui_group = "Part" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(fillHolesSettings, fill_pattern="hex") + + self.buildArgParser(x=320, y=220) + + + def xHoles(self): +# border = [(5, 10), (245, 10), (225, 150), (235, 150), (255, 10), (290, 10), (270, 190), (45, 190), (45, 50), (35, 50), (35, 190), (5, 190)] + + x, y = self.x, self.y + + border = [ + ( 5/320*x, 10/220*y), + (245/320*x, 10/220*y), + (225/320*x, 150/220*y), + (235/320*x, 150/220*y), + (255/320*x, 10/220*y), + (290/320*x, 10/220*y), + (270/320*x, 190/220*y), + ( 45/320*x, 190/220*y), + ( 45/320*x, 50/220*y), + ( 35/320*x, 50/220*y), + ( 35/320*x, 190/220*y), + ( 5/320*x, 190/220*y), + ] + + + self.showBorderPoly(border) + self.text("Area to be filled", x/2, 190/220*y, align="bottom center", color=Color.ANNOTATIONS) + + start_time = time.time() + self.fillHoles( + pattern=self.fillHoles_fill_pattern, + border=border, + max_radius=self.fillHoles_hole_max_radius, + hspace=self.fillHoles_space_between_holes, + bspace=self.fillHoles_space_to_border, + min_radius=self.fillHoles_hole_min_radius, + style=self.fillHoles_hole_style, + bar_length=self.fillHoles_bar_length, + max_random=self.fillHoles_max_random + ) + end_time = time.time() + +# print('fillHoles - Execution time:', (end_time-start_time)*1000, 'ms ', self.fillHoles_fill_pattern) + + def render(self): + self.rectangularWall(self.x, self.y, "eeee", callback=[self.xHoles, None, None, None],)
+ + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/flexbox.html b/html/_modules/boxes/generators/flexbox.html new file mode 100644 index 0000000..64df117 --- /dev/null +++ b/html/_modules/boxes/generators/flexbox.html @@ -0,0 +1,215 @@ + + + + + + + + boxes.generators.flexbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.flexbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import boxes
+import math
+
+
+
[docs]class FlexBox(boxes.Boxes): + """Box with living hinge and round corners""" + + ui_group = "FlexBox" + + def __init__(self): + boxes.Boxes.__init__(self) + self.addSettingsArgs(boxes.edges.FingerJointSettings) + self.addSettingsArgs(boxes.edges.FlexSettings) + self.buildArgParser("x", "y", "h", "outside") + self.argparser.add_argument( + "--radius", action="store", type=float, default=15, + help="Radius of the latch in mm") + self.argparser.add_argument( + "--latchsize", action="store", type=float, default=8, + help="size of latch in multiples of thickness") + + def flexBoxSide(self, x, y, r, callback=None, move=None): + t = self.thickness + + if self.move(x+2*t, y+t, move, True): + return + + self.moveTo(t+r, t) + + for i, l in zip(range(2), (x, y)): + self.cc(callback, i) + self.edges["f"](l - 2 * r) + self.corner(90, r) + + self.cc(callback, 2) + self.edge(x - 2 * r) + self.corner(90, r) + self.cc(callback, 3) + self.latch(self.latchsize) + self.cc(callback, 4) + self.edges["f"](y - 2 * r - self.latchsize) + self.corner(90, r) + + self.move(x+2*t, y+t, move) + + def surroundingWall(self, move=None): + x, y, h, r = self.x, self.y, self.h, self.radius + t = self.thickness + c4 = math.pi * r * 0.5 + + tw = 2*x + 2*y - 8*r + 4*c4 + th = h + 2.5*t + + if self.move(tw, th, move, True): + return + + self.moveTo(0, 0.25*t) + + self.edges["F"](y - 2 * r - self.latchsize, False) + if x - 2 * r < t: + self.edges["X"](2 * c4 + x - 2 * r, h + 2 * t) + else: + self.edges["X"](c4, h + 2 * t) + self.edges["F"](x - 2 * r, False) + self.edges["X"](c4, h + 2 * t) + self.edges["F"](y - 2 * r, False) + if x - 2 * r < t: + self.edges["X"](2 * c4 + x - 2 * r, h + 2 * t) + else: + self.edges["X"](c4, h + 2 * t) + self.edge(x - 2 * r) + self.edges["X"](c4, h + 2 * t) + self.latch(self.latchsize, False) + self.edge(h + 2 * t) + self.latch(self.latchsize, False, True) + self.edge(c4) + self.edge(x - 2 * r) + self.edge(c4) + self.edges["F"](y - 2 * r, False) + self.edge(c4) + self.edges["F"](x - 2 * r, False) + self.edge(c4) + self.edges["F"](y - 2 * r - self.latchsize, False) + self.corner(90) + self.edge(h + 2 * t) + self.corner(90) + + self.move(tw, th, move) + + def render(self): + + if self.outside: + self.x = self.adjustSize(self.x) + self.y = self.adjustSize(self.y) + self.h = self.adjustSize(self.h) + + x, y, h = self.x, self.y, self.h + self.latchsize *= self.thickness + r = self.radius or min(x, y - self.latchsize) / 2.0 + r = min(r, x / 2.0) + self.radius = r = min(r, max(0, (y - self.latchsize) / 2.0)) + + + self.surroundingWall(move="up") + self.flexBoxSide(self.x, self.y, self.radius, move="right") + self.flexBoxSide(self.x, self.y, self.radius, move="mirror")
+ + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/flexbox2.html b/html/_modules/boxes/generators/flexbox2.html new file mode 100644 index 0000000..2f4d73b --- /dev/null +++ b/html/_modules/boxes/generators/flexbox2.html @@ -0,0 +1,211 @@ + + + + + + + + boxes.generators.flexbox2 — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.flexbox2

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+import math
+
+
[docs]class FlexBox2(Boxes): + """Box with living hinge and top corners rounded""" + + ui_group = "FlexBox" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.addSettingsArgs(edges.FlexSettings) + self.buildArgParser("x", "y", "h", "outside") + self.argparser.add_argument( + "--radius", action="store", type=float, default=15, + help="Radius of the corners in mm") + self.argparser.add_argument( + "--latchsize", action="store", type=float, default=8, + help="size of latch in multiples of thickness") + + def flexBoxSide(self, y, h, r, callback=None, move=None): + t = self.thickness + if self.move(y+2*t, h+t, move, True): + return + + self.moveTo(t, t) + self.cc(callback, 0) + self.edges["f"](y) + self.corner(90, 0) + self.cc(callback, 1) + self.edges["f"](h - r) + self.corner(90, r) + self.cc(callback, 2) + self.edge(y - 2 * r) + self.corner(90, r) + self.cc(callback, 3) + self.latch(self.latchsize) + self.cc(callback, 4) + self.edges["f"](h - r - self.latchsize) + self.corner(90) + + self.move(y+2*t, h+t, move) + + def surroundingWall(self, move=None): + y, h, x, r = self.y, self.h, self.x, self.radius + t = self.thickness + + tw = y + h - 3*r + 2*self.c4 + self.latchsize + t + th = x + 2.5*t + + if self.move(tw, th, move, True): + return + + self.moveTo(t, .25*t) + self.edges["F"](h - r, False) + + if (y - 2 * r < t): + self.edges["X"](2 * self.c4 + y - 2 * r, x + 2 * t) + else: + self.edges["X"](self.c4, x + 2 * t) + self.edge(y - 2 * r) + self.edges["X"](self.c4, x + 2 * t) + + self.latch(self.latchsize, False) + self.edge(x + 2 * t) + self.latch(self.latchsize, False, True) + self.edge(self.c4) + self.edge(y - 2 * r) + self.edge(self.c4) + self.edges["F"](h - r) + self.corner(90) + self.edge(t) + self.edges["f"](x) + self.edge(t) + self.corner(90) + + self.move(tw, th, move) + + def render(self): + + if self.outside: + self.y = self.adjustSize(self.y) + self.h = self.adjustSize(self.h) + self.x = self.adjustSize(self.x) + + self.latchsize *= self.thickness + self.radius = self.radius or min(self.y / 2.0, self.h - self.latchsize) + self.radius = min(self.radius, self.y / 2.0) + self.radius = min(self.radius, max(0, self.h - self.latchsize)) + self.c4 = c4 = math.pi * self.radius * 0.5 + + + self.moveTo(2 * self.thickness, self.thickness) + + with self.saved_context(): + self.surroundingWall(move="right") + self.rectangularWall(self.y, self.x, edges="FFFF") + + self.surroundingWall(move="up only") + + self.flexBoxSide(self.y, self.h, self.radius, move="right") + self.flexBoxSide(self.y, self.h, self.radius, move= "mirror right") + self.rectangularWall(self.x, self.h - self.radius - self.latchsize, edges="fFeF")
+ + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/flexbox3.html b/html/_modules/boxes/generators/flexbox3.html new file mode 100644 index 0000000..42f9570 --- /dev/null +++ b/html/_modules/boxes/generators/flexbox3.html @@ -0,0 +1,258 @@ + + + + + + + + boxes.generators.flexbox3 — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.flexbox3

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+import math
+
+
+
[docs]class FlexBox3(Boxes): + """Box with living hinge""" + + ui_group = "FlexBox" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings, surroundingspaces=1) + self.addSettingsArgs(edges.FlexSettings) + self.buildArgParser("x", "y", "outside") + self.argparser.add_argument( + "--z", action="store", type=float, default=100.0, + help="height of the box") + self.argparser.add_argument( + "--h", action="store", type=float, default=10.0, + help="height of the lid") + self.argparser.add_argument( + "--radius", action="store", type=float, default=10.0, + help="radius of the lids living hinge") + self.argparser.add_argument( + "--c", action="store", type=float, default=1.0, + dest="d", help="clearance of the lid") + + def flexBoxSide(self, x, y, r, callback=None, move=None): + t = self.thickness + if self.move(x+2*t, y+t, move, True): + return + + self.moveTo(t, t) + self.cc(callback, 0) + self.edges["f"](x) + self.corner(90, 0) + self.cc(callback, 1) + self.edges["f"](y - r) + self.corner(90, r) + self.cc(callback, 2) + self.edge(x - r) + self.corner(90, 0) + self.cc(callback, 3) + self.edges["f"](y) + self.corner(90) + + self.move(x+2*t, y+t, move) + + def surroundingWall(self, move=None): + x, y, z, r, d = self.x, self.y, self.z, self.radius, self.d + t = self.thickness + + tw = x + y - 2*r + self.c4 + 2*t + t + th = z + 4*t + 2*d + + if self.move(tw, th, move, True): + return + + self.moveTo(t, d + t) + + self.edges["F"](y - r, False) + self.edges["X"](self.c4, z + 2 * t) + self.corner(-90) + self.edge(d) + self.corner(90) + self.edges["f"](x - r + t) + self.corner(90) + self.edges["f"](z + 2 * t + 2 * d) + self.corner(90) + self.edges["f"](x - r + t) + self.corner(90) + self.edge(d) + self.corner(-90) + self.edge(self.c4) + self.edges["F"](y - r) + self.corner(90) + self.edge(t) + self.edges["f"](z) + self.edge(t) + self.corner(90) + + self.move(tw, th, move) + + def lidSide(self, move=None): + x, y, z, r, d, h = self.x, self.y, self.z, self.radius, self.d, self.h + t = self.thickness + r2 = r + t if r + t <= h + t else h + t + + if r < h: + r2 = r + t + base_l = x + 2 * t + if self.move(h+t, base_l+t, move, True): + return + + self.edge(h + self.thickness - r2) + self.corner(90, r2) + self.edge(r - r2 + 1 * t) + else: + a = math.acos((r-h)/(r+t)) + ang = math.degrees(a) + base_l = x + (r+t) * math.sin(a) - r + t + if self.move(h+t, base_l+t, move, True): + return + + self.corner(90-ang) + self.corner(ang, r+t) + + self.edges["F"](x - r + t) + self.edgeCorner("F", "f") + self.edges["g"](h) + self.edgeCorner("f", "e") + self.edge(base_l) + self.corner(90) + + self.move(h+t, base_l+t, move) + + def render(self): + if self.outside: + self.x = self.adjustSize(self.x) + self.y = self.adjustSize(self.y) + self.z = self.adjustSize(self.z) + + x, y, z, d, h = self.x, self.y, self.z, self.d, self.h + r = self.radius = self.radius or min(x, y) / 2.0 + thickness = self.thickness + + self.c4 = c4 = math.pi * r * 0.5 * 0.95 + self.latchsize = 8 * thickness + + width = 2 * x + y - 2 * r + c4 + 14 * thickness + 3 * h # lock + height = y + z + 8 * thickness + + + s = edges.FingerJointSettings(self.thickness, finger=1., + space=1., surroundingspaces=1) + s.edgeObjects(self, "gGH") + + with self.saved_context(): + self.surroundingWall(move="right") + self.rectangularWall(x, z, edges="FFFF", move="right") + self.rectangularWall(h, z + 2 * (d + self.thickness), edges="GeGF", move="right") + self.lidSide(move="right") + self.lidSide(move="mirror right") + + self.surroundingWall(move="up only") + + self.flexBoxSide(x, y, r, move="right") + self.flexBoxSide(x, y, r, move="mirror right") + self.rectangularWall(z, y, edges="fFeF")
+ + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/flexbox4.html b/html/_modules/boxes/generators/flexbox4.html new file mode 100644 index 0000000..70f6d9d --- /dev/null +++ b/html/_modules/boxes/generators/flexbox4.html @@ -0,0 +1,210 @@ + + + + + + + + boxes.generators.flexbox4 — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.flexbox4

+#!/usr/bin/env python3
+# Copyright (C) 2013-2018 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+import math
+
+
+
[docs]class FlexBox4(Boxes): + """Box with living hinge and left corners rounded""" + + ui_group = "FlexBox" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.addSettingsArgs(edges.FlexSettings) + self.buildArgParser("x", "y", "h", "outside") + self.argparser.add_argument( + "--radius", action="store", type=float, default=15, + help="Radius of the corners in mm") + self.argparser.add_argument( + "--latchsize", action="store", type=float, default=8, + help="size of latch in multiples of thickness") + + def flexBoxSide(self, x, y, r, callback=None, move=None): + t = self.thickness + + if self.move(x+2*t, y+t, move, True): + return + + self.moveTo(t, t) + + self.cc(callback, 0) + self.edges["f"](x) + self.corner(90, 0) + self.cc(callback, 1) + self.edges["f"](y - r) + self.corner(90, r) + self.cc(callback, 2) + self.edge(x - 2 * r) + self.corner(90, r) + self.cc(callback, 3) + self.edges["e"](y - r - self.latchsize) + self.cc(callback, 4) + self.latch(self.latchsize) + self.corner(90) + + self.move(x+2*t, y+t, move) + + def surroundingWall(self, move=None): + x, y, h, r = self.x, self.y, self.h, self.radius + c4 = self.c4 + + t = self.thickness + + tw, th = 2*c4 + 2*y + x - 4*r + 2*t, h + 2.5*t + + if self.move(tw, th, move, True): + return + + self.moveTo(t, 0.25*t) + + self.edges["F"](y - r, False) + if (x - 2 * r < self.thickness): + self.edges["X"](2 * c4 + x - 2 * r, h + 2 * self.thickness) + else: + self.edges["X"](c4, h + 2 * self.thickness) + self.edge(x - 2 * r) + self.edges["X"](c4, h + 2 * self.thickness) + + self.edge(y - r - self.latchsize) + self.latch(self.latchsize+t, False) + self.edge(h + 2 * self.thickness) + self.latch(self.latchsize+t, False, True) + self.edge(y - r - self.latchsize) + self.edge(c4) + self.edge(x - 2 * r) + self.edge(c4) + self.edges["F"](y - r) + self.corner(90) + self.edge(self.thickness) + self.edges["f"](h) + self.edge(self.thickness) + self.corner(90) + + self.move(tw, th, move) + + def render(self): + if self.outside: + self.x = self.adjustSize(self.x) + self.y = self.adjustSize(self.y) + self.h = self.adjustSize(self.h) + + self.latchsize *= self.thickness + self.radius = self.radius or min(self.x / 2.0, self.y - self.latchsize) + self.radius = min(self.radius, self.x / 2.0) + self.radius = min(self.radius, max(0, self.y - self.latchsize)) + self.c4 = c4 = math.pi * self.radius * 0.5 + + + self.surroundingWall(move="up") + self.flexBoxSide(self.x, self.y, self.radius, move="right") + self.flexBoxSide(self.x, self.y, self.radius, move="mirror right") + self.rectangularWall(self.x, self.h, edges="FeFF")
+ + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/flexbox5.html b/html/_modules/boxes/generators/flexbox5.html new file mode 100644 index 0000000..3151347 --- /dev/null +++ b/html/_modules/boxes/generators/flexbox5.html @@ -0,0 +1,212 @@ + + + + + + + + boxes.generators.flexbox5 — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.flexbox5

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import boxes
+import math
+
+
+
[docs]class FlexBox5(boxes.Boxes): + """Box with living hinge and round corners""" + + ui_group = "FlexBox" + + def __init__(self): + boxes.Boxes.__init__(self) + self.addSettingsArgs(boxes.edges.FingerJointSettings) + self.addSettingsArgs(boxes.edges.FlexSettings) + self.buildArgParser("x", "h", "outside") + self.argparser.add_argument( + "--top_diameter", action="store", type=float, default=60, + help="diameter at the top") + self.argparser.add_argument( + "--bottom_diameter", action="store", type=float, default=60, + help="diameter at the bottom") + self.argparser.add_argument( + "--latchsize", action="store", type=float, default=8, + help="size of latch in multiples of thickness") + + def flexBoxSide(self, callback=None, move=None): + t = self.thickness + + r1, r2 = self.top_diameter/2., self.bottom_diameter/2 + a = self.a + l = self.l + + tw , th = l+r1+r2, 2*max(r1, r2)+2*t + + if self.move(tw, th, move, True): + return + + self.moveTo(r2, t) + + self.cc(callback, 0) + self.edges["f"](l) + self.corner(180+2*a, r1) + self.cc(callback, 1) + self.latch(self.latchsize) + self.cc(callback, 2) + self.edges["f"](l - self.latchsize) + self.corner(180-2*a, r2) + + self.move(tw, th, move) + + def surroundingWall(self, move=None): + t = self.thickness + + r1, r2 = self.top_diameter/2., self.bottom_diameter/2 + h = self.h + a = self.a + l = self.l + + + c1 = math.radians(180+2*a) * r1 + c2 = math.radians(180-2*a) * r2 + + tw = 2*l + c1 + c2 + th = h + 2.5*t + + if self.move(tw, th, move, True): + return + + self.moveTo(0, 0.25*t) + + self.edges["F"](l - self.latchsize, False) + self.edges["X"](c2, h + 2 * t) + self.edges["F"](l, False) + self.edges["X"](c1, h + 2 * t) + self.latch(self.latchsize, False) + self.edge(h + 2 * t) + self.latch(self.latchsize, False, True) + self.edge(c1) + self.edges["F"](l, False) + self.edge(c2) + self.edges["F"](l - self.latchsize, False) + self.corner(90) + self.edge(h + 2 * t) + self.corner(90) + + self.move(tw, th, move) + + def render(self): + + if self.outside: + self.x = self.adjustSize(self.x) + self.h = self.adjustSize(self.h) + self.top_diameter = self.adjustSize(self.top_diameter) + self.bottom_diameter = self.adjustSize(self.bottom_diameter) + + t = self.thickness + self.latchsize *= self.thickness + d_t, d_b = self.top_diameter, self.bottom_diameter + self.x = max(self.x, self.latchsize + 2*t + (d_t + d_b)/2) + + d_c = self.x - d_t/2. - d_b/2. + self.a = math.degrees(math.asin((d_t-d_b)/2 / d_c)) + self.l = d_c * math.cos(math.radians(self.a)) + + self.surroundingWall(move="up") + self.flexBoxSide(move="right") + self.flexBoxSide(move="mirror")
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/flextest.html b/html/_modules/boxes/generators/flextest.html new file mode 100644 index 0000000..1e00a55 --- /dev/null +++ b/html/_modules/boxes/generators/flextest.html @@ -0,0 +1,136 @@ + + + + + + + + boxes.generators.flextest — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.flextest

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
+
[docs]class FlexTest(Boxes): + "Piece for testing different flex settings" + + ui_group = "Part" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FlexSettings) + self.buildArgParser("x", "y") + + def render(self): + x, y = self.x, self.y + + self.moveTo(5, 5) + self.edge(10) + self.edges["X"](x, y) + self.edge(10) + self.corner(90) + self.edge(y) + self.corner(90) + self.edge(x + 20) + self.corner(90) + self.edge(y) + self.corner(90)
+ + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/flextest2.html b/html/_modules/boxes/generators/flextest2.html new file mode 100644 index 0000000..ea6e12b --- /dev/null +++ b/html/_modules/boxes/generators/flextest2.html @@ -0,0 +1,127 @@ + + + + + + + + boxes.generators.flextest2 — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.flextest2

+#!/usr/bin/python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
+
[docs]class FlexTest2(Boxes): + "Piece for testing 2D flex settings" + + ui_group = "Part" + + def __init__(self): + Boxes.__init__(self) + self.buildArgParser("x", "y") + self.argparser.add_argument( + "--fw", action="store", type=float, default=1, + help="distance of flex cuts in multiples of thickness") + + def render(self): + x, y = self.x, self.y + + self.rectangularWall(x, y, callback=[lambda: self.flex2D(x, y, self.fw)])
+ + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/folder.html b/html/_modules/boxes/generators/folder.html new file mode 100644 index 0000000..1258bce --- /dev/null +++ b/html/_modules/boxes/generators/folder.html @@ -0,0 +1,139 @@ + + + + + + + + boxes.generators.folder — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.folder

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+import math
+
+
+
[docs]class Folder(Boxes): + """Book cover with flex for the spine""" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FlexSettings) + self.buildArgParser("x", "y", "h") + self.argparser.add_argument( + "--r", action="store", type=float, default=10.0, + help="radius of the corners") + self.argparser.set_defaults(h=20) + + def render(self): + x, y, r, h = self.x, self.y, self.r, self.h + c2 = math.pi * h + self.moveTo(r + self.thickness, self.thickness) + self.edge(x - r) + self.edges["X"](c2, y) + self.edge(x - r) + self.corner(90, r) + self.edge(y - 2 * r) + self.corner(90, r) + self.edge(2 * x - 2 * r + c2) + self.corner(90, r) + self.edge(y - 2 * r) + self.corner(90, r)
+ + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/gear.html b/html/_modules/boxes/generators/gear.html new file mode 100644 index 0000000..d162631 --- /dev/null +++ b/html/_modules/boxes/generators/gear.html @@ -0,0 +1,186 @@ + + + + + + + + boxes.generators.gear — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.gear

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class Gears(Boxes): + """Gears""" + + ui_group = "Part" + + def __init__(self): + Boxes.__init__(self) + self.argparser.add_argument( + "--teeth1", action="store", type=int, default=12, + help="number of teeth") + self.argparser.add_argument( + "--shaft1", action="store", type=float, default=6., + help="diameter of the shaft 1") + self.argparser.add_argument( + "--dpercentage1", action="store", type=float, default=75, + help="percent of the D section of shaft 1 (100 for round shaft)") + + self.argparser.add_argument( + "--teeth2", action="store", type=int, default=32, + help="number of teeth in the other size of gears") + self.argparser.add_argument( + "--shaft2", action="store", type=float, default=0.0, + help="diameter of the shaft2 (zero for same as shaft 1)") + self.argparser.add_argument( + "--dpercentage2", action="store", type=float, default=0, + help="percent of the D section of shaft 1 (0 for same as shaft 1)") + + self.argparser.add_argument( + "--modulus", action="store", type=float, default=2, + help="size of teeth (diameter / #teeth) in mm") + self.argparser.add_argument( + "--pressure_angle", action="store", type=float, default=20, + help="angle of the teeth touching (in degrees)") + self.argparser.add_argument( + "--profile_shift", action="store", type=float, default=20, + help="in precent of the modulus") + + def render(self): + # adjust to the variables you want in the local scope + t = self.thickness + + self.teeth1 = max(2, self.teeth1) + self.teeth2 = max(2, self.teeth2) + + if not self.shaft2: + self.shaft2 = self.shaft1 + if not self.dpercentage2: + self.dpercentage2 = self.dpercentage1 + + self.gears(teeth=self.teeth2, dimension=self.modulus, + angle=self.pressure_angle, profile_shift=self.profile_shift, + callback=lambda:self.dHole(0, 0, d=self.shaft2, + rel_w=self.dpercentage2/100.), + move="up") + r2, d2, d2 = self.gears.sizes( + teeth=self.teeth2, dimension=self.modulus, + angle=self.pressure_angle, profile_shift=self.profile_shift) + + self.gears(teeth=self.teeth1, dimension=self.modulus, + angle=self.pressure_angle, profile_shift=self.profile_shift, + callback=lambda:self.dHole(0, 0, d=self.shaft1, + rel_w=self.dpercentage1/100.), + move="up") + r1, d1, d1 = self.gears.sizes( + teeth=self.teeth1, dimension=self.modulus, + angle=self.pressure_angle, profile_shift=self.profile_shift) + r = max(self.shaft1, self.shaft2)/2 + self.hole(t+r, t+r, self.shaft1/2) + self.hole(t+r+r1+r2, t+r, self.shaft2/2) + self.moveTo(0, 2*r+t) + + self.text("""Pitch radius 1: %.1fmm +Outer diameter 1: %.1fmm +Pitch radius 2: %.1fmm +Outer diameter 2: %.1fmm +Axis distance: %.1fmm + """ % (r1, d1, r2, d2, r1+r2), align="bottom left")
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/gearbox.html b/html/_modules/boxes/generators/gearbox.html new file mode 100644 index 0000000..03d4c3b --- /dev/null +++ b/html/_modules/boxes/generators/gearbox.html @@ -0,0 +1,188 @@ + + + + + + + + boxes.generators.gearbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.gearbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
+
[docs]class GearBox(Boxes): + """Gearbox with multiple identical stages""" + + ui_group = "Part" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.argparser.add_argument( + "--teeth1", action="store", type=int, default=8, + help="number of teeth on ingoing shaft") + self.argparser.add_argument( + "--teeth2", action="store", type=int, default=20, + help="number of teeth on outgoing shaft") + self.argparser.add_argument( + "--modulus", action="store", type=float, default=3, + help="modulus of the theeth in mm") + self.argparser.add_argument( + "--shaft", action="store", type=float, default=6., + help="diameter of the shaft") + self.argparser.add_argument( + "--stages", action="store", type=int, default=4, + help="number of stages in the gear reduction") + + def render(self): + + if self.teeth2 < self.teeth1: + self.teeth2, self.teeth1 = self.teeth1, self.teeth2 + + pitch1, size1, xxx = self.gears.sizes(teeth=self.teeth1, dimension=self.modulus) + pitch2, size2, xxx = self.gears.sizes(teeth=self.teeth2, dimension=self.modulus) + + t = self.thickness + x = 1.1 * t * self.stages + + if self.stages == 1: + y = size1 + size2 + y1 = y / 2 - (pitch1 + pitch2) + pitch1 + y2 = y / 2 + (pitch1 + pitch2) - pitch2 + else: + y = 2 * size2 + y1 = y / 2 - (pitch1 + pitch2) / 2 + y2 = y / 2 + (pitch1 + pitch2) / 2 + + h = max(size1, size2) + t + + b = "F" + t = "e" # prepare for close box + mh = self.shaft + + def sideCB(): + self.hole(y1, h / 2, mh / 2) + self.hole(y2, h / 2, mh / 2) + + self.moveTo(self.thickness, self.thickness) + self.rectangularWall(y, h, [b, "f", t, "f"], callback=[sideCB], move="right") + self.rectangularWall(x, h, [b, "F", t, "F"], move="up") + self.rectangularWall(x, h, [b, "F", t, "F"]) + self.rectangularWall(y, h, [b, "f", t, "f"], callback=[sideCB], move="left") + self.rectangularWall(x, h, [b, "F", t, "F"], move="up only") + + self.rectangularWall(x, y, "ffff", move="up") + + profile_shift = 20 + pressure_angle = 20 + + for i in range(self.stages - 1): + self.gears(teeth=self.teeth2, dimension=self.modulus, angle=pressure_angle, + mount_hole=mh, profile_shift=profile_shift, move="up") + + self.gears(teeth=self.teeth2, dimension=self.modulus, angle=pressure_angle, + mount_hole=mh, profile_shift=profile_shift, move="right") + + for i in range(self.stages): + self.gears(teeth=self.teeth1, dimension=self.modulus, angle=pressure_angle, + mount_hole=mh, profile_shift=profile_shift, move="down")
+ + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/halfbox.html b/html/_modules/boxes/generators/halfbox.html new file mode 100644 index 0000000..8c0667f --- /dev/null +++ b/html/_modules/boxes/generators/halfbox.html @@ -0,0 +1,245 @@ + + + + + + + + boxes.generators.halfbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.halfbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class HalfBox(Boxes): + """Configurable half of a box which can be: a bookend, a hanging shelf, an angle clamping jig, ...""" + + description = """This can be used to create: + +* a hanging shelf: +![HalfBox as hanging shelf](static/samples/HalfBox_Shelf_usage.jpg) + +* an angle clamping jig: +![HalfBox as an angle clamping jig](static/samples/HalfBox_AngleJig_usage.jpg) + +* a bookend: +![HalfBox as a bookend](static/samples/HalfBox_Bookend_usage.jpg) + +and many more... + +""" + ui_group = "Box" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings, finger=2.0,space=2.0) + self.addSettingsArgs(edges.MountingSettings) + self.buildArgParser(x=100, sy="50:50", h=100) + + self.argparser.add_argument("--Clamping", action="store", type=boolarg, default=False, help="add clamping holes") + self.argparser.add_argument("--ClampingSize", action="store", type=float, default=25.0, help="diameter of clamping holes") + self.argparser.add_argument("--Mounting", action="store", type=boolarg, default=False, help="add mounting holes") + self.argparser.add_argument("--Sturdy", action="store", type=boolarg, default=False, help="create sturdy construction (e.g. shelf, clamping jig, ...)") + + def polygonWallExt(self, borders, edge="f", turtle=False, callback=None, move=None): + # extended polygon wall. + # same as polygonWall, but with extended border parameters + # each border dataset consists of + # length + # turn angle + # radius of turn (without radius correction) + # edge type + + for i in range(0, len(borders), 4): + self.cc(callback, i) + length = borders[i] + next_angle = borders[i+1] + next_radius = borders[i+2] + next_edge = borders[i+3] + + e = self.edges.get(next_edge, next_edge) + if i == 0: + self.moveTo(0,e.margin(),0) + e(length) + if self.debug: + self.hole(0, 0, 1, color=Color.ANNOTATIONS) + self.corner(next_angle, tabs=0, radius=next_radius) + + def xHoles(self): + posy = -0.5 * self.thickness + for y in self.sy[:-1]: + posy += y + self.thickness + self.fingerHolesAt(posy, 0, self.x) + + def hHoles(self): + posy = -0.5 * self.thickness + for y in reversed(self.sy[1:]): + posy += y + self.thickness + self.fingerHolesAt(posy, 0, self.h) + + def render(self): + # adjust to the variables you want in the local scope + + x, h = self.x, self.h + d = self.ClampingSize + t = self.thickness + + # triangle with sides: x (horizontal), h (upwards) and l + # angles: 90° between x & h + # b between h & l + # c between l & x + + l = math.sqrt(x * x + h * h) + b = math.degrees(math.asin(x / l)) + c = math.degrees(math.asin(h / l)) + if x > h: + if 90 + b + c < 179: + b = 180 - b + else: + if 90 + b + c < 179: + c = 180 - c + + # small triangle top: 2*t, h1, l1 + h1 = (2*t)/x*h + l1 = (2*t)/x*l + + # small triangle left: x2, 2*t, l2 + x2 = (2*t)/h*x + l2 = (2*t)/h*l + + # render your parts here + + if self.Sturdy: + width = sum(self.sy) + (len(self.sy) - 1) * t + self.rectangularWall(x, width, "fffe", callback=[None, self.xHoles, None, None], move="right", label="bottom") + self.rectangularWall(h, width, "fGfF" if self.Mounting else "fefF", callback=[None, None, None, self.hHoles], move="up", label="back") + self.rectangularWall(x, width, "fffe", callback=[None, self.xHoles, None, None], move="left only", label="invisible") + + for i in range(2): + self.move(x+x2+2*t + self.edges["f"].margin(), h+h1+2*t + self.edges["f"].margin(), "right", True, label="side " + str(i)) + self.polygonWallExt(borders=[x2, 0, 0, "e", x, 0, 0, "h",2*t, 90, 0, "e", 2*t, 0, 0, "e", h, 0, 0, "h",h1, 180-b, 0, "e", l+l1+l2, 180-c, 0, "e"]) + if self.Clamping: + self.hole(0, 0, 1, color=Color.ANNOTATIONS) + self.rectangularHole(x/2+x2,2*t+d/2,dx=d,dy=d,r=d/8) + self.rectangularHole((x+x2+2*t)-2*t-d/2,h/2+2*t,dx=d,dy=d,r=d/8) + self.move(x+x2+2*t + self.edges["f"].margin(), h+h1+2*t + self.edges["f"].margin(), "right", False, label="side " + str(i)) + + if len(self.sy) > 1: + for i in range((len(self.sy) - 1)): + self.move(x + self.edges["f"].margin(), h + self.edges["f"].margin(), "right", True, label="support " + str(i)) + self.polygonWallExt(borders=[x, 90, 0, "f", h, 180-b, 0, "f", l, 180-c, 0, "e"]) + if self.Clamping: + self.rectangularHole(x/2,d/2-t/2,dx=d,dy=d+t,r=d/8) + self.rectangularHole(x-d/2+t/2,h/2,dx=d+t,dy=d,r=d/8) + self.move(x + self.edges["f"].margin(), h + self.edges["f"].margin(), "right", False, label="support " + str(i)) + else: + self.sy.insert(0,0) + self.sy.append(0) + width = sum(self.sy) + (len(self.sy) - 1) * t + self.rectangularWall(x, width, "efee", callback=[None, self.xHoles, None, None], move="right", label="bottom") + self.rectangularWall(h, width, "eGeF" if self.Mounting else "eeeF", callback=[None, None, None, self.hHoles], move="up", label="side") + self.rectangularWall(x, width, "efee", callback=[None, self.xHoles, None, None], move="left only", label="invisible") + + for i in range((len(self.sy) - 1)): + self.move(x + self.edges["f"].margin(), h + self.edges["f"].margin(), "right", True, label="support " + str(i)) + self.polygonWallExt(borders=[x, 90, 0, "f", h, 180-b, 0, "f", l, 180-c, 0, "e"]) + if self.Clamping: + self.rectangularHole(x/2,d/2,dx=d,dy=d,r=d/8) + self.rectangularHole(x-d/2,h/2,dx=d,dy=d,r=d/8) + self.move(x + self.edges["f"].margin(), h + self.edges["f"].margin(), "right", False, label="support " + str(i))
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/heart.html b/html/_modules/boxes/generators/heart.html new file mode 100644 index 0000000..6778b5e --- /dev/null +++ b/html/_modules/boxes/generators/heart.html @@ -0,0 +1,160 @@ + + + + + + + + boxes.generators.heart — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.heart

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class HeartBox(Boxes): + """Box in the form of an heart""" + + ui_group = "FlexBox" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings, finger=1.0,space=1.0) + self.addSettingsArgs(edges.FlexSettings) + self.buildArgParser(x=150, h=50) + self.argparser.add_argument( + "--top", action="store", type=str, default="closed", + choices=["closed", "hole", "lid",], + help="style of the top and lid") + + def CB(self): + x = self.x + t = self.thickness + + l = 2/3. * x - t + r = l/2. - t + d = 2 *t + + if self.top == "closed": + return + + for i in range(2): + self.moveTo(t, t) + self.polyline((l, 2), (180, r), (d, 1), -90, + (d, 1), (180, r), (l, 2), 90) + l -= t + r -= t + d += t + if self.top == "hole": + return + + def render(self): + x, h = self.x, self.h + t = self.thickness + + l = 2/3. * x + r = l/2. - 0.5*t + + borders = [l, (180, r), t, -90, t, (180, r), l, 90] + self.polygonWalls(borders, h) + self.rectangularWall(0, h, "FFFF", move="up only") + self.polygonWall(borders, callback=[self.CB], move="right") + self.moveTo(-2*t) + self.polygonWall(borders, move="mirror right") + if self.top == "lid": + self.polygonWall([l+t, (180, r+t), 0, -90, 0, (180, r+t), l+t, 90], 'e')
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/hingebox.html b/html/_modules/boxes/generators/hingebox.html new file mode 100644 index 0000000..92a90d7 --- /dev/null +++ b/html/_modules/boxes/generators/hingebox.html @@ -0,0 +1,174 @@ + + + + + + + + boxes.generators.hingebox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.hingebox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class HingeBox(Boxes): + """Box with lid attached by cabinet hinges""" + + ui_group = "Box" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.addSettingsArgs(edges.CabinetHingeSettings) + self.buildArgParser("x", "y", "h", "outside") + self.argparser.add_argument( + "--lidheight", action="store", type=float, default=20.0, + help="height of lid in mm") + self.argparser.add_argument( + "--splitlid", action="store", type=float, default=0.0, + help="split the lid in y direction (mm)") + + def render(self): + + x, y, h, hl = self.x, self.y, self.h, self.lidheight + s = self.splitlid + + if self.outside: + x = self.adjustSize(x) + y = self.adjustSize(y) + h = self.adjustSize(h) + s = self.adjustSize(s, None) # reduce by half of the walls + + if s > x or s < 0.0: s = 0.0 + t = self.thickness + + # bottom walls + if s: + self.rectangularWall(x, h, "FFuF", move="right") + else: + self.rectangularWall(x, h, "FFeF", move="right") + self.rectangularWall(y, h, "Ffef", move="up") + self.rectangularWall(y, h, "Ffef") + self.rectangularWall(x, h, "FFuF", move="left up") + + # lid + self.rectangularWall(x, hl, "UFFF", move="right") + if s: + self.rectangularWall(s, hl, "eeFf", move="right") + self.rectangularWall(y-s, hl, "efFe", move="up") + self.rectangularWall(y-s, hl, "eeFf") + self.rectangularWall(s, hl, "efFe", move="left") + self.rectangularWall(x, hl, "UFFF", move="left up") + else: + self.rectangularWall(y, hl, "efFf", move="up") + self.rectangularWall(y, hl, "efFf") + self.rectangularWall(x, hl, "eFFF", move="left up") + + self.rectangularWall(x, y, "ffff", move="right only") + self.rectangularWall(x, y, "ffff") + if s: + self.rectangularWall(x, s, "ffef", move="left up") + self.rectangularWall(x, y-s, "efff", move="up") + else: + self.rectangularWall(x, y, "ffff", move="left up") + self.edges['u'].parts(move="up") + if s: + self.edges['u'].parts(move="up")
+ + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/holepattern.html b/html/_modules/boxes/generators/holepattern.html new file mode 100644 index 0000000..46f6f36 --- /dev/null +++ b/html/_modules/boxes/generators/holepattern.html @@ -0,0 +1,167 @@ + + + + + + + + boxes.generators.holepattern — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.holepattern

+#!/usr/bin/env python3
+# Copyright (C) 2013-2022 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from shapely.geometry import *
+import random
+import time
+
+
[docs]class HolePattern(Boxes): + """Generate hole patterns in different simple shapes""" + + ui_group = "Holes" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(fillHolesSettings, fill_pattern="hex") + + self.buildArgParser("x", "y") + self.argparser.add_argument( + "--shape", action="store", type=str, default="rectangle", + choices=["rectangle", "ellipse", "oval", "hexagon", "octagon"], + help="Shape of the hole pattern") + + def render(self): + x, y = self.x, self.y + + if self.shape == "ellipse": + border = [(x * (.5+math.sin(math.radians(a))/2), + y * (.5+math.cos(math.radians(a))/2)) + for a in range(0, 360, 18)] + elif self.shape == "oval": + r = min(x, y) / 2 + dx = max(x-y, 0) + dy = max(y-x, 0) + border = [(r * (.5+math.cos(math.radians(a))/2) + + (dx if q in [0, 3] else 0), + r * (.5+math.sin(math.radians(a))/2) + + (dy if q in [1, 2] else 0)) + for q in range(4) + for a in range(90*q, 90*q+91, 18)] + elif self.shape == "hexagon": + dx = min(y / (3**.5) / 2, x / 2) + border = [(dx, 0), (x-dx, 0), (x, .5*y), + (x-dx, y), (dx, y), (0, .5*y)] + elif self.shape == "octagon": + d = (2**.5/(2+2*2**.5)) + d2 = 1 -d + border = [(d*x, 0), (d2*x, 0), (x, d*y), (x, d2*y), + (d2*x, y), (d*x, y), (0, d2*y), (0, d*y)] + else: # "rectangle" + border = [(0, 0), (x, 0), (x, y), (0, y)] + + self.fillHoles( + pattern=self.fillHoles_fill_pattern, + border=border, + max_radius=self.fillHoles_hole_max_radius, + hspace=self.fillHoles_space_between_holes, + bspace=self.fillHoles_space_to_border, + min_radius=self.fillHoles_hole_min_radius, + style=self.fillHoles_hole_style, + bar_length=self.fillHoles_bar_length, + max_random=self.fillHoles_max_random + )
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/hooks.html b/html/_modules/boxes/generators/hooks.html new file mode 100644 index 0000000..2001ecf --- /dev/null +++ b/html/_modules/boxes/generators/hooks.html @@ -0,0 +1,213 @@ + + + + + + + + boxes.generators.hooks — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.hooks

+#!/usr/bin/env python3
+# Copyright (C) 2013-2018 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+import math
+
+
[docs]class Hook(Boxes): + """A hook wit a rectangular mouth to mount at the wall""" + + ui_group = "Misc" # see ./__init__.py for names + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings, surroundingspaces=0.5) + self.argparser.add_argument("--width", action="store", + type=float, default=40., + help="width of the hook (back plate is a bit wider)") + self.argparser.add_argument("--height", action="store", + type=float, default=40., + help="inner height of the hook") + self.argparser.add_argument("--depth", action="store", + type=float, default=40., + help="inner depth of the hook") + self.argparser.add_argument("--strength", action="store", + type=float, default=20., + help="width of the hook from the side") + self.argparser.add_argument("--angle", action="store", + type=float, default=45., + help="angle of the support underneeth") + + def render(self): + + self.angle = min(self.angle, 80) + t = self.thickness + w = self.width - 2*t # inner width + d, h, s = self.depth, self.height, self.strength + + self.rectangularWall(w + 4*t, self.height_back, 'Eeee', callback=self.back_callback, move='right') + self.sidewall(d, h, s, self.angle, move='right') + self.sidewall(d, h, s, self.angle, move='right') + self.rectangularWall(d, w, 'FFFf', move='right') + self.rectangularWall(h - t, w, 'FFFf', move='right', callback=[ + lambda: self.hole((h - t)/2, w/2, d=17)]) + self.rectangularWall(s-t, w, 'FeFf', move='right') + + + + def back_callback(self, n): + if n != 0: + return + + t = self.thickness + h = self.h_a + self.strength + + self.fingerHolesAt(1.5*t, 0, h) + self.fingerHolesAt(self.width + .5*t, 0, h) + self.fingerHolesAt(2*t, h + t/2, self.width - 2*t, 0) + + x_h = self.width/2 + t + + y1 = h + self.height/2 + y2 = self.strength/2 + y3 = (y1 + y2) / 2 + + self.hole(x_h, y1, d=3) + self.hole(x_h, y2, d=3) + self.hole(x_h, y3, d=3) + + + @property + def height_back(self): + + return self.strength + self.height + self.h_a + + @property + def h_a(self): + return self.depth * math.tan(math.radians(self.angle)) + + def sidewall(self, depth, height, strength, angle=60., move=None): + + t = self.thickness + + h_a = depth * math.tan(math.radians(angle)) + l_a = depth / math.cos(math.radians(angle)) + + f_edge = self.edges['f'] + + x_total = depth + strength + f_edge.margin() + y_total = strength + height + h_a + + if self.move(x_total, y_total, move, before=True): + return + + + self.moveTo(f_edge.margin()) + self.polyline(strength, angle, l_a, 90-angle, height+strength, 90) + + f_edge(strength - t) + self.corner(90) + f_edge(height - t) + + self.polyline(t, -90, t) + + f_edge(depth) + self.corner(90) + f_edge(h_a + strength) + self.corner(90) + + self.move(x_total, y_total, move)
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/integratedhingebox.html b/html/_modules/boxes/generators/integratedhingebox.html new file mode 100644 index 0000000..c2ef21f --- /dev/null +++ b/html/_modules/boxes/generators/integratedhingebox.html @@ -0,0 +1,156 @@ + + + + + + + + boxes.generators.integratedhingebox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.integratedhingebox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
+
[docs]class IntegratedHingeBox(Boxes): + """Box with lid and integraded hinge""" + + ui_group = "Box" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.addSettingsArgs(edges.ChestHingeSettings) + self.buildArgParser("x", "y", "h", "outside") + self.argparser.add_argument( + "--lidheight", action="store", type=float, default=20.0, + help="height of lid in mm") + + + def render(self): + + x, y, h, hl = self.x, self.y, self.h, self.lidheight + + if self.outside: + x = self.adjustSize(x) + y = self.adjustSize(y) + h = self.adjustSize(h) + + t = self.thickness + + hy = self.edges["O"].startwidth() + hy2 = self.edges["P"].startwidth() + + e1 = edges.CompoundEdge(self, "Fe", (h-hy, hy)) + e2 = edges.CompoundEdge(self, "eF", (hy, h-hy)) + e_back = ("F", e1, "e", e2) + + self.rectangularWall(y, h-hy, "FfOf", ignore_widths=[2], move="up") + self.rectangularWall(y, hl-hy2, "pfFf", ignore_widths=[1], move="up") + self.rectangularWall(y, h-hy, "Ffof", ignore_widths=[5], move="up") + self.rectangularWall(y, hl-hy2, "PfFf", ignore_widths=[6], move="up") + self.rectangularWall(x, h, "FFeF", move="up") + self.rectangularWall(x, h, e_back, move="up") + self.rectangularWall(x, hl, "FFeF", move="up") + self.rectangularWall(x, hl-hy2, "FFqF", move="up") + + self.rectangularWall(y, x, "ffff", move="up") + self.rectangularWall(y, x, "ffff")
+ + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/jointpanel.html b/html/_modules/boxes/generators/jointpanel.html new file mode 100644 index 0000000..32e5f7c --- /dev/null +++ b/html/_modules/boxes/generators/jointpanel.html @@ -0,0 +1,155 @@ + + + + + + + + boxes.generators.jointpanel — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.jointpanel

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class JointPanel(Boxes): + """Create pieces larger than your laser cutter by joining them with Dove Tails""" + + description = """This can be used to just create a big panel in a smaller laser cutter. But the actual use is to split large parts into multiple smaller pieces. Copy the outline onto the sheet and then use the pieces to cut it into multiple parts that each can fit your laser cutter. Note that each piece must be cut with the sheet surrounding it to ensure the burn correction (aka kerf) is correct. Depending on your vector graphics software you may need to duplicate your part multiple times and then generate the intersection between one copy and each rectangular part. + +The Boxes.py drawings assume that the laser is cutting in the center of the line and the width of the line represents the material that is cut away. Make sure your changes work the same way and you do not cutting away the kerf. + +Small dove tails make it easier to fit parts in without problems. Lookout for pieces cut loose where the dove tails meet the edge of the parts. Move your part if necessary to avoid dove tails or details of your part colliding in a weird way. + +For plywood this method works well with a very stiff press fit. Aim for needing a hammer to join the pieces together. This way they will feel like they have been welder together. + +""" + + ui_group = "Misc" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs( + edges.DoveTailSettings, size=1, depth=.5, radius=.1) + self.buildArgParser(sx="400/2", sy="400/3") + self.argparser.add_argument( + "--separate", action="store", type=boolarg, default=False, + help="draw pieces apart so they can be cut to form a large sheet") + + def render(self): + sx, sy = self.sx, self.sy + t = self.thickness + + for ny, y in enumerate(sy): + t0 = "e" if ny == 0 else "d" + t2 = "e" if ny == len(sy) - 1 else "D" + with self.saved_context(): + for nx, x in enumerate(sx): + t1 = "e" if nx == len(sx) - 1 else "d" + t3 = "e" if nx == 0 else "D" + self.rectangularWall(x, y, [t0, t1, t2, t3]) + if self.separate: + self.rectangularWall(x, y, [t0, t1, t2, t3], + move="right only") + else: + self.moveTo(x) + if self.separate: + self.rectangularWall(x, y, [t0, t1, t2, t3], + move="up only") + else: + self.moveTo(0, y - self.edges["d"].spacing() if ny == 0 else y)
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/keypad.html b/html/_modules/boxes/generators/keypad.html new file mode 100644 index 0000000..ce72f4a --- /dev/null +++ b/html/_modules/boxes/generators/keypad.html @@ -0,0 +1,229 @@ + + + + + + + + boxes.generators.keypad — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.keypad

+"""Generator for keypads with mechanical switches."""
+
+from copy import deepcopy
+
+from boxes import Boxes, boolarg
+from boxes.edges import FingerJointSettings
+from .keyboard import Keyboard
+
+
+
[docs]class Keypad(Boxes, Keyboard): + """Generator for keypads with mechanical switches.""" + + description = "Note that top layers use a different material thickness according to the top1_thickness and top2_thickness (if enabled)." + + ui_group = 'Box' + btn_size = 15.6 + space_between_btn = 4 + box_padding = 10 + triangle = 25.0 + + def __init__(self): + super().__init__() + self.argparser.add_argument( + '--h', action='store', type=int, default=30, + help='height of the box' + ) + self.argparser.add_argument( + '--top1_thickness', action='store', type=float, default=1.5, + help=('thickness of the button hold layer, cherry like switches ' + 'need 1.5mm or smaller to snap in') + ) + self.argparser.add_argument( + '--top2_enable', action='store', type=boolarg, default=False, + help=('enables another top layer that can hold CPG151101S11 ' + 'hotswap sockets') + ) + self.argparser.add_argument( + '--top2_thickness', action='store', type=float, default=1.5, + help=('thickness of the hotplug layer, CPG151101S11 hotswap ' + 'sockets need 1.2mm to 1.5mm') + ) + + # Add parameter common with other keyboard projects + self.add_common_keyboard_parameters( + # Hotswap already depends on top2_enable setting, a second parameter + # for it would be useless + add_hotswap_parameter=False, + # By default, 3 columns of 4 rows + default_columns_definition="4x3" + ) + + self.addSettingsArgs(FingerJointSettings, surroundingspaces=1) + + def _get_x_y(self): + """Gets the keypad's size based on the number of buttons.""" + spacing = self.btn_size + self.space_between_btn + border = 2*self.box_padding - self.space_between_btn + x = len(self.columns_definition) * spacing + border + y = max(offset + keys * spacing for (offset, keys) in self.columns_definition) + border + return x, y + + def render(self): + """Renders the keypad.""" + # deeper edge for top to add multiple layers + deep_edge = deepcopy(self.edges['f'].settings) + deep_edge.thickness = self.thickness + self.top1_thickness + if self.top2_enable: + deep_edge.thickness += self.top2_thickness + deep_edge.edgeObjects(self, 'gGH', True) + + d1, d2 = 2., 3. + x, y = self._get_x_y() + h = self.h + + # box sides + self.rectangularWall(x, h, "GFEF", callback=[self.wallx_cb], move="right") + self.rectangularWall(y, h, "GfEf", callback=[self.wally_cb], move="up") + self.rectangularWall(y, h, "GfEf", callback=[self.wally_cb]) + self.rectangularWall(x, h, "GFEF", callback=[self.wallx_cb], move="left up") + + # keypad lids + self.rectangularWall(x, y, "ffff", callback=self.to_grid_callback(self.support_hole), move="right") + self.rectangularWall(x, y, "ffff", callback=self.to_grid_callback(self.key_hole), move="up") + if self.top2_enable: + self.rectangularWall(x, y, "ffff", callback=self.to_grid_callback(self.hotplug)) + + # screwable + tr = self.triangle + trh = tr / 3 + self.rectangularWall( + x, y, + callback=[lambda: self.hole(trh, trh, d=d2)] * 4, + move='left up' + ) + self.rectangularTriangle( + tr, tr, "ffe", num=4, + callback=[None, lambda: self.hole(trh, trh, d=d1)] + ) + + def to_grid_callback(self, inner_callback): + def callback(): + # move to first key center + key_margin = self.box_padding + self.btn_size / 2 + self.moveTo(key_margin, key_margin) + self.apply_callback_on_columns( + inner_callback, self.columns_definition, self.btn_size + self.space_between_btn + ) + + return [callback] + + def hotplug(self): + """Callback for the key stabelizers.""" + self.pcb_holes( + with_pcb_mount=self.pcb_mount_enable, + with_diode=self.diode_enable, + with_led=self.led_enable, + ) + + def support_hole(self): + self.configured_plate_cutout(support=True) + + def key_hole(self): + self.configured_plate_cutout() + + # stolen form electronics-box + def wallx_cb(self): + """Callback for triangle holes on x-side.""" + x, _ = self._get_x_y() + t = self.thickness + self.fingerHolesAt(0, self.h - 1.5 * t, self.triangle, 0) + self.fingerHolesAt(x, self.h - 1.5 * t, self.triangle, 180) + + # stolen form electronics-box + def wally_cb(self): + """Callback for triangle holes on y-side.""" + _, y = self._get_x_y() + t = self.thickness + self.fingerHolesAt(0, self.h - 1.5 * t, self.triangle, 0) + self.fingerHolesAt(y, self.h - 1.5 * t, self.triangle, 180)
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/laptopstand.html b/html/_modules/boxes/generators/laptopstand.html new file mode 100644 index 0000000..a409590 --- /dev/null +++ b/html/_modules/boxes/generators/laptopstand.html @@ -0,0 +1,290 @@ + + + + + + + + boxes.generators.laptopstand — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.laptopstand

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from math import *
+
+
+
[docs]class LaptopStand(Boxes): # Change class name! + """A simple X shaped frame to support a laptop on a given angle""" + + ui_group = "Misc" # see ./__init__.py for names + + def __init__(self): + Boxes.__init__(self) + + self.argparser.add_argument( + "--l_depth", + action="store", + type=float, + default=250, + help="laptop depth - front to back (mm)", + ) + self.argparser.add_argument( + "--l_thickness", + action="store", + type=float, + default=10, + help="laptop thickness (mm)", + ) + self.argparser.add_argument( + "--angle", + action="store", + type=float, + default=15, + help="desired tilt of keyboard (deg)", + ) + self.argparser.add_argument( + "--ground_offset", + action="store", + type=float, + default=10, + help="desired height between bottom of laptop and ground at lowest point (front of laptop stand)", + ) + self.argparser.add_argument( + "--nub_size", + action="store", + type=float, + default=10, + help="desired thickness of the supporting edge", + ) + + def render(self): + calcs = self.perform_calculations() + + self.laptopstand_triangles(calcs, move="up") + + def perform_calculations(self): + # a + angle_rads_a = math.radians(self.angle) + + # h + height = self.l_depth * math.sin(angle_rads_a) + + # y + base = sqrt(2) * self.l_depth * math.cos(angle_rads_a) + + # z + hyp = self.l_depth * sqrt(math.pow(math.cos(angle_rads_a), 2) + 1) + + # b + angle_rads_b = math.atan(math.tan(angle_rads_a) / math.sqrt(2)) + + # g + base_extra = ( + 1 + / math.cos(angle_rads_b) + * (self.nub_size - self.ground_offset * math.sin(angle_rads_b)) + ) + + # x + lip_outer = ( + self.ground_offset / math.cos(angle_rads_b) + + self.l_thickness + - self.nub_size * math.tan(angle_rads_b) + ) + + bottom_slot_depth = (height / 4) + (self.ground_offset / 2) + + top_slot_depth_big = ( + height / 4 + self.ground_offset / 2 + (self.thickness * height) / (2 * base) + ) + + top_slot_depth_small = ( + height / 4 + self.ground_offset / 2 - (self.thickness * height) / (2 * base) + ) + + half_hyp = (hyp * (base - self.thickness)) / (2 * base) + + return dict( + height=height, + base=base, + hyp=hyp, + angle=math.degrees(angle_rads_b), + base_extra=base_extra, + lip_outer=lip_outer, + bottom_slot_depth=bottom_slot_depth, + top_slot_depth_small=top_slot_depth_small, + top_slot_depth_big=top_slot_depth_big, + half_hyp=half_hyp, + ) + + def laptopstand_triangles(self, calcs, move=None): + tw = calcs["base"] + self.spacing + 2 * (calcs["base_extra"] + math.sin(math.radians(calcs["angle"]))*(calcs["lip_outer"]+1)) + th = calcs["height"] + 2 * self.ground_offset + self.spacing + + if self.move(tw, th, move, True): + return + self.moveTo(calcs["base_extra"]+self.spacing + math.sin(math.radians(calcs["angle"]))*(calcs["lip_outer"]+1)) + self.draw_triangle(calcs, top=False) + self.moveTo(calcs["base"] - self.spacing, + th, 180) + self.draw_triangle(calcs, top=True) + + self.move(tw, th, move) + + @restore + def draw_triangle(self, calcs, top): + # Rear end + self.moveTo(0, calcs["height"] + self.ground_offset, -90) + + self.edge(calcs["height"] + self.ground_offset) + self.corner(90) + + foot_length = 10 + self.nub_size + + base_length_without_feet = ( + calcs["base"] - foot_length * 2 - 7 # -7 to account for extra width gained by 45deg angles + ) + + if top: + # Bottom without slot + self.polyline( + foot_length, 45, + 5, -45, + base_length_without_feet, -45, + 5, 45, + foot_length + calcs["base_extra"], 0, + ) + else: + # Bottom with slot + self.polyline( + foot_length, 45, + 5, -45, + (base_length_without_feet - self.thickness) / 2, 90, + calcs["bottom_slot_depth"] - 3.5, -90, + self.thickness, -90, + calcs["bottom_slot_depth"] - 3.5, 90, + (base_length_without_feet - self.thickness) / 2, -45, + 5, 45, + foot_length + calcs["base_extra"], 0, + ) + + # End nub + self.corner(90 - calcs["angle"]) + self.edge(calcs["lip_outer"]) + self.corner(90, 1) + self.edge(self.nub_size - 2) + self.corner(90, 1) + self.edge(self.l_thickness) + self.corner(-90) + + if top: + # Top with slot + self.edge(calcs["half_hyp"]) + self.corner(90 + calcs["angle"]) + self.edge(calcs["top_slot_depth_small"]) + self.corner(-90) + self.edge(self.thickness) + self.corner(-90) + self.edge(calcs["top_slot_depth_big"]) + self.corner(90 - calcs["angle"]) + self.edge(calcs["half_hyp"]) + else: + # Top without slot + self.edge(calcs["hyp"]) + + self.corner(90 + calcs["angle"])
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/laserclamp.html b/html/_modules/boxes/generators/laserclamp.html new file mode 100644 index 0000000..2f2b22d --- /dev/null +++ b/html/_modules/boxes/generators/laserclamp.html @@ -0,0 +1,187 @@ + + + + + + + + boxes.generators.laserclamp — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.laserclamp

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class LaserClamp(Boxes): + """A clamp to hold down material to a knife table""" + + description = """You need a tension spring of the proper length to make the clamp work. +Increace extraheight to get more space for the spring and to make the +sliding mechanism less likely to bind. You may need to add some wax on the +parts sliding on each other to reduce friction. +""" + + ui_group = "Misc" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings, surroundingspaces=0) + self.argparser.add_argument( + "--minheight", action="store", type=float, default=25., + help="minimal clamping height in mm") + self.argparser.add_argument( + "--maxheight", action="store", type=float, default=50., + help="maximal clamping height in mm") + self.argparser.add_argument( + "--extraheight", action="store", type=float, default=0., + help="extra height to make operation smoother in mm") + + def topPart(self, l, move=None): + t = self. thickness + + tw, th = 12*t, l+4*t + + if self.move(tw, th, move, True): + return + + self.moveTo(8*t, 0) + self.rectangularHole(t, 2*t+l/2, 1.05*t, l) + self.polyline(2*t, (90, t), l+1.5*t, (-90, 0.5*t), + 2*t, -90, 0, (180, 0.5*t), 0, + (90, 1.5*t), 9*t, + (180, 4*t), 2*t, (-90, t)) + self.hole(-5*t, -3*t, 2.5*t) + self.polyline(l-5.5*t, (90, t)) + self.move(tw, th, move) + + def bottomPart(self, h_min, h_extra, move=None): + t = self. thickness + + tw, th = 14*t, h_min+4*t + + if self.move(tw, th, move, True): + return + + ls = t/2*(2**.5) + self.moveTo(2*t, 0) + self.fingerHolesAt(3*t, 2*t, h_min+h_extra, 90) + if h_extra: + self.polyline(4*t, (90,t), h_extra-2*t, (-90, t)) + else: + self.polyline(6*t) + self.polyline(4*t, (90, 2*t), 3*t, 135, 2*ls, 45, 1*t, -90, 6*t, -90) + + self.polyline(h_min, (90, t), 2*t, (90, t), + h_min+h_extra-0*t, (-90, t), t, (180, t), + 0, 90, 0, (-180, 0.5*t), 0 , 90) + + self.move(tw, th, move) + + def render(self): + t = self. thickness + h_max, h_min, h_extra = self.maxheight, self.minheight,self.extraheight + + if h_extra and h_extra < 2*t: + h_extra = 2*t + + self.topPart(h_max+h_extra, move="right") + self.bottomPart(h_min, h_extra, move="right") + self.roundedPlate(4*t, h_min+h_extra+4*t, edge="e", r=t, + extend_corners=False, move="right", + callback=[lambda: self.fingerHolesAt(1*t, 2*t, h_min+h_extra)]) + self.rectangularWall(1.1*t, h_min+h_extra, "efef")
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/laserholdfast.html b/html/_modules/boxes/generators/laserholdfast.html new file mode 100644 index 0000000..06e687a --- /dev/null +++ b/html/_modules/boxes/generators/laserholdfast.html @@ -0,0 +1,133 @@ + + + + + + + + boxes.generators.laserholdfast — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.laserholdfast

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class LaserHoldfast(Boxes): + """A holdfast for honey comb tables of laser cutters""" + + ui_group = "Part" + + def __init__(self): + Boxes.__init__(self) + + self.buildArgParser(x=25, h=40) + self.argparser.add_argument( + "--hookheight", action="store", type=float, default=5.0, + help="height of the top hook") + self.argparser.add_argument( + "--shaftwidth", action="store", type=float, default=5.0, + help="width of the shaft") + + def render(self): + # adjust to the variables you want in the local scope + x, hh, h, sw = self.x, self.hookheight, self.h, self.shaftwidth + t = self.thickness + + a = 30 + r = x/math.radians(a) + + self.polyline(hh+h, (180, sw/2), h, -90+a/2, 0, (-a, r), 0, (180, hh/2), 0, (a, r+hh), 0 , -a/2, sw-math.sin(math.radians(a/2))*hh , 90)
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/lbeam.html b/html/_modules/boxes/generators/lbeam.html new file mode 100644 index 0000000..b8f7f47 --- /dev/null +++ b/html/_modules/boxes/generators/lbeam.html @@ -0,0 +1,132 @@ + + + + + + + + boxes.generators.lbeam — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.lbeam

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class LBeam(Boxes): + """Simple L-Beam: two pieces joined with a right angle""" + + ui_group = "Part" + + def __init__(self): + Boxes.__init__(self) + self.buildArgParser("x", "y", "h", "outside") + self.addSettingsArgs(edges.FingerJointSettings) + + def render(self): + x, y, h = self.x, self.y, self.h + t = self.thickness + + if self.outside: + x = self.adjustSize(x, False) + y = self.adjustSize(y, False) + + + + self.rectangularWall(x, h, "eFee", move="right") + self.rectangularWall(y, h, "eeef")
+ + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/magazinefile.html b/html/_modules/boxes/generators/magazinefile.html new file mode 100644 index 0000000..00db925 --- /dev/null +++ b/html/_modules/boxes/generators/magazinefile.html @@ -0,0 +1,182 @@ + + + + + + + + boxes.generators.magazinefile — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.magazinefile

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes.lids import _TopEdge
+
+
[docs]class MagazinFile(Boxes): + """Open magazine file""" + + def __init__(self): + Boxes.__init__(self) + self.buildArgParser(x=100, y=200, h=300, hi=0, outside=False) + self.addSettingsArgs(edges.FingerJointSettings) + self.addSettingsArgs(edges.MountingSettings, margin=0, num=1) + self.argparser.add_argument( + "--top_edge", action="store", + type=ArgparseEdgeType("eG"), choices=list("eG"), + default="e", help="edge type for top edge") + + def side(self, w, h, hi, top_edge): + r = min(h - hi, w) / 2.0 + + if (h - hi) > w: + r = w / 2.0 + lx = 0 + ly = (h - hi) - w + else: + r = (h - hi) / 2.0 + lx = (w - 2 * r) / 2.0 + ly = 0 + + top_edge = self.edges.get(top_edge, top_edge) + + e_w = self.edges["F"].startwidth() + self.moveTo(3, 3) + self.edge(e_w) + self.edges["F"](w) + self.edge(e_w) + self.corner(90) + self.edge(e_w) + self.edges["F"](hi) + self.corner(90) + self.edge(e_w) + top_edge(lx) + self.corner(-90, r) + self.edge(ly) + self.corner(90, r) + top_edge(lx) + self.edge(e_w) + self.corner(90) + self.edges["F"](h) + self.edge(e_w) + self.corner(90) + + def render(self): + + if self.outside: + self.x = self.adjustSize(self.x) + self.y = self.adjustSize(self.y) + self.h = self.adjustSize(self.h, e2=False) + + x, y, h, = self.x, self.y, self.h + self.hi = hi = self.hi or (h / 2.0) + t = self.thickness + t1, t2, t3, t4 = _TopEdge.topEdges(self, self.top_edge) + + + with self.saved_context(): + self.rectangularWall(x, h, ["F", "f", t2, "f"], move="up") + self.rectangularWall(x, hi, "Ffef", move="up") + self.rectangularWall(x, y, "ffff") + + self.rectangularWall(x, h, "Ffef", move="right only") + self.side(y, h, hi, t1) + self.moveTo(y + 15, h + hi + 15, 180) + self.side(y, h, hi, t3)
+ + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/makitapowersupply.html b/html/_modules/boxes/generators/makitapowersupply.html new file mode 100644 index 0000000..3aa66a6 --- /dev/null +++ b/html/_modules/boxes/generators/makitapowersupply.html @@ -0,0 +1,223 @@ + + + + + + + + boxes.generators.makitapowersupply — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.makitapowersupply

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class MakitaPowerSupply(Boxes): + """Bench power supply powered with Maktia 18V battery or laptop power supply""" + + description = """ +Vitamins: DSP5005 (or similar) power supply, two banana sockets, two 4.8mm flat terminals with flat soldering tag + +To allow powering by laptop power supply: flip switch, Lenovo round socket (or adjust right hole for different socket) +""" + + ui_group = "Misc" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings) + + self.argparser.add_argument("--banana_socket_diameter", action="store", + type=float, default=8.0, + help="diameter of the banana socket mounting holes") + + self.argparser.add_argument("--flipswitch_diameter", action="store", + type=float, default=6.3, + help="diameter of the flipswitch mounting hole") + + + def side(self, l, h=14, move=None): + t = self.thickness + tw, th = h+t, l + + if self.move(tw, th, move, True): + return + + self.moveTo(t, 0) + self.polyline(h, 90, l-h/3**0.5, 60, h*2/3**0.5, 120) + self.edges["f"](l) + + self.move(tw, th, move) + + def side2(self, l, h=14, move=None): + t = self.thickness + tw, th = h, l-10 + + if self.move(tw, th, move, True): + return + + if h > 14: + self.polyline(h, 90, l-12, 90, h-14, 90, 50-12, -90, 8, -90) + else: + self.polyline(h, 90, l-50, 90, h-6, -90) + self.polyline(11, 90, 1, -90, 27, (90, 1), + 3, (90, 1), l-12, 90) + + self.move(tw, th, move) + + + def bottom(self): + t = self.thickness + m = self.x / 2 + + self.fingerHolesAt(m-30.5-0.5*t, 10, self.l) + self.fingerHolesAt(m+30.5+0.5*t, 10, self.l) + + self.rectangularHole(m-19, 10+34, 0.8, 6.25) + self.rectangularHole(m+19, 10+34, 0.8, 6.25) + + self.rectangularHole(m, 7.5, 35, 5) + + def front(self): + d_b = self.banana_socket_diameter + d_f = self.flipswitch_diameter + + self.hole(10, self.h/2, d=d_b) + self.hole(30, self.h/2, d=d_b) + self.hole(50, self.h/2, d=d_f) + + self.rectangularHole(76, 6.4, 12.4, 12.4) + + def back(self): + n = int((self.h-2*self.thickness) // 8) + offs = (self.h - n*8.0) / 2 + 4 + for i in range(n): + self.rectangularHole(self.x/2, i*8+offs, self.x-20, 5, r=2.5) + + def regulatorCB(self): + self.rectangularHole(21, 9.5, 35, 5) + self.rectangularHole(5, 33+12, 10, 10) + self.rectangularHole(42-5, 33+12, 10, 10) + + for x in [3.5, 38.5]: + for y in [3.5, 65]: + self.hole(x, y, 1.0) + + def render(self): + # adjust to the variables you want in the local scope + t = self.thickness + + l = self.l = 64 + hm = 15.5 + + self.x, self.y, self.h = x, y, h = 85, 75, 35 + + self.rectangularWall(x, h, "FFFF", callback=[self.front], move="right") + self.rectangularWall(y, h, "FfFf", move="up") + self.rectangularWall(y, h, "FfFf") + self.rectangularWall(x, h, "FFFF", callback=[self.back], move="left up") + + self.rectangularWall(x, y, "ffff", callback=[self.bottom], move="right") + self.rectangularWall(x, y, "ffff", callback=[ + lambda: self.rectangularHole(x/2, y-20-5, 76, 40)], move="") + self.rectangularWall(x, y, "ffff", move="left up only") + + self.side(l, hm, move="right") + self.side(l, hm, move="right mirror") + self.side2(l, hm, move="right") + self.side2(l, hm, move="right mirror")
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/microrack.html b/html/_modules/boxes/generators/microrack.html new file mode 100644 index 0000000..5e5501c --- /dev/null +++ b/html/_modules/boxes/generators/microrack.html @@ -0,0 +1,292 @@ + + + + + + + + boxes.generators.microrack — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.microrack

+#!/usr/bin/env python3
+# Copyright (C) 2019 Gabriel Morell
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+import decimal
+
+from boxes import Boxes, edges, boolarg
+
+
+
[docs]class SBCMicroRack(Boxes): + """Stackable rackable racks for SBC Pi-Style Computers""" + + webinterface = True + ui_group = "Shelf" # see ./__init__.py for names + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings) + self.addSettingsArgs(edges.StackableSettings) + + self.buildArgParser(x=56, y=85) + + # count + self.argparser.add_argument( + "--sbcs", action="store", type=int, default=5, + help="how many slots for sbcs", + ) + + # spaces + self.argparser.add_argument( + "--clearance_x", action="store", type=int, default=3, + help="clearance for the board in the box (x) in mm" + ) + self.argparser.add_argument( + "--clearance_y", action="store", type=int, default=3, + help="clearance for the board in the box (y) in mm" + ) + self.argparser.add_argument( + "--clearance_z", action="store", type=int, default=28, + help="SBC Clearance in mm", + ) + + # mounting holes + self.argparser.add_argument( + "--hole_dist_edge", action="store", type=float, default=3.5, + help="hole distance from edge in mm" + ) + self.argparser.add_argument( + "--hole_grid_dimension_x", action="store", type=int, default=58, + help="width of x hole area" + ) + self.argparser.add_argument( + "--hole_grid_dimension_y", action="store", type=int, default=49, + help="width of y hole area" + ) + self.argparser.add_argument( + "--hole_diameter", action="store", type=float, default=2.75, + help="hole diameters" + ) + + # i/o holes + self.argparser.add_argument( + "--netusb_z", action="store", type=int, default=18, + help="height of the net/usb hole mm" + ) + self.argparser.add_argument( + "--netusb_x", action="store", type=int, default=53, + help="width of the net/usb hole in mm" + ) + + # features + self.argparser.add_argument( + "--stable", action='store', type=boolarg, default=False, + help="draw some holes to put a 1/4\" dowel through at the base and top" + ) + self.argparser.add_argument( + "--switch", action='store', type=boolarg, default=False, + help="adds an additional vertical segment to hold the switch in place, works best w/ --stable" + ) + # TODO flesh this idea out better + #self.argparser.add_argument( + # "--fan", action='store', type=int, default=0, required=False, + # help="ensure that the x width is at least this much and as well, draw a snug holder for a fan someplace" + # ) + + + def paint_mounting_holes(self): + cy = self.clearance_y + cx = self.clearance_x + + h2r = self.hole_diameter + hde = self.hole_dist_edge + hgdx = self.hole_grid_dimension_x + hgdy = self.hole_grid_dimension_y + + self.hole( + h2r + cx + hde / 2, + h2r + cy + hde / 2, + h2r / 2 + ) + + self.hole( + h2r + cx + hgdx + hde / 2, + h2r + cy + hde / 2, + h2r / 2 + ) + + self.hole( + h2r + cx + hde / 2, + h2r + cy + hgdy + hde / 2, + h2r / 2 + ) + + self.hole( + h2r + cx + hgdx + hde / 2, + h2r + cy + hgdy + hde / 2, + h2r / 2 + ) + + def paint_stable_features(self): + if self.stable: + self.hole( + 10, 10, d=6.5 + ) + + def paint_netusb_holes(self): + t = self.thickness + x = self.x + w = x + self.hole_dist_edge * 2 + height_per = self.clearance_z + t + usb_height = self.netusb_z + usb_width = self.netusb_x + for i in range(self.sbcs): + self.rectangularHole(w/2, (height_per)*i+15 , usb_width, usb_height, r=1) + + def paint_finger_holes(self): + t = self.thickness + height_per = self.clearance_z + t + for i in range(self.sbcs): + self.fingerHolesAt((height_per) * i + +height_per/2 + 1.5, self.hole_dist_edge, self.x, 90) + + def render(self): + # adjust to the variables you want in the local scope + x, y = self.x, self.y + t = self.thickness + + height_per = self.clearance_z + t + height_total = self.sbcs * height_per + + # render your parts here + + with self.saved_context(): + self.rectangularWall(height_total + height_per/2, + x + self.hole_dist_edge * 2, + "eseS", + callback=[self.paint_finger_holes, + self.paint_netusb_holes], + move="up") + + self.rectangularWall(height_total + height_per/2, + x + self.hole_dist_edge * 2, + "eseS", + callback=[self.paint_finger_holes, + self.paint_stable_features], + move="up") + + if self.switch: + self.rectangularWall(height_total + height_per / 2, + x + self.hole_dist_edge * 2, + "eseS", + callback=[self.paint_stable_features], + move="up") + + self.rectangularWall(height_total + height_per/2, + x + self.hole_dist_edge * 2, + "eseS", + move="right only") + + self.rectangularWall(y + self.hole_dist_edge * 2, + x + self.hole_dist_edge * 2, + "efef", + move="up") + + for i in range(self.sbcs): + self.rectangularWall(y + self.hole_dist_edge * 2, + x + self.hole_dist_edge * 2, + "efef", + callback=[self.paint_mounting_holes], + move="up")
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/nemamount.html b/html/_modules/boxes/generators/nemamount.html new file mode 100644 index 0000000..3806f51 --- /dev/null +++ b/html/_modules/boxes/generators/nemamount.html @@ -0,0 +1,145 @@ + + + + + + + + boxes.generators.nemamount — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.nemamount

+#!/usr/bin/env python3
+# Copyright (C) 2013-2017 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class NemaMount(Boxes): + """Mounting braket for a Nema motor""" + + ui_group = "Part" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.argparser.add_argument( + "--size", action="store", type=int, default=8, + choices=list(sorted(self.nema_sizes.keys())), + help="Nema size of the motor") + + def render(self): + motor, flange, holes, screws = self.nema_sizes.get( + self.size, self.nema_sizes[8]) + t = self.thickness + + x = y = h = motor + 2*t + + + self.rectangularWall(x, y, "ffef", callback=[ + lambda: self.NEMA(self.size, x/2, y/2)], move="right") + self.rectangularTriangle(x, h, "fFe", num=2, move="right") + self.rectangularWall(x, h, "FFeF", callback=[ + + lambda:self.rectangularHole((x-holes)/2, y/2, screws, holes, + screws/2), + None, + lambda:self.rectangularHole((x-holes)/2, y/2, screws, holes, + screws/2)], + move="right") + self.moveTo(t, 0) + self.fingerHolesAt(0.5*t, t, x, 90) + self.fingerHolesAt(1.5*t+x, t, x, 90) + self.fingerHolesAt(t, 0.5*t, x, 0)
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/nemapattern.html b/html/_modules/boxes/generators/nemapattern.html new file mode 100644 index 0000000..92edd17 --- /dev/null +++ b/html/_modules/boxes/generators/nemapattern.html @@ -0,0 +1,129 @@ + + + + + + + + boxes.generators.nemapattern — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.nemapattern

+#!/usr/bin/env python3
+# Copyright (C) 2013-2017 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class NemaPattern(Boxes): + """Mounting holes for a Nema motor""" + + ui_group = "Holes" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.argparser.add_argument( + "--size", action="store", type=int, default=8, + choices=list(sorted(self.nema_sizes.keys())), + help="Nema size of the motor") + self.argparser.add_argument( + "--screwholes", action="store", type=float, default=0.0, + help="Size of the screw holes in mm - 0 for default size") + + def render(self): + motor, flange, holes, screws = self.nema_sizes.get( + self.size, self.nema_sizes[8]) + + self.NEMA(self.size, motor/2, motor/2, screwholes=self.screwholes)
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/notesholder.html b/html/_modules/boxes/generators/notesholder.html new file mode 100644 index 0000000..9dea530 --- /dev/null +++ b/html/_modules/boxes/generators/notesholder.html @@ -0,0 +1,198 @@ + + + + + + + + boxes.generators.notesholder — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.notesholder

+#!/usr/bin/env python3
+# Copyright (C) 2013-2019 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes.edges import Edge
+
+class USlotEdge(Edge):
+
+    def __call__(self, length, bedBolts=None, bedBoltSettings=None, **kw):
+        l = length
+        d = self.settings
+        r = min(3*self.thickness, (l-2*d)/2)
+        self.edges["f"](d)
+        self.polyline(0, 90, 0, (-90, r), l-2*d-2*r, (-90, r), 0, 90)
+        self.edges["f"](d)
+
+    def margin(self):
+        return self.edges["f"].margin()
+
+class HalfStackableEdge(edges.StackableEdge):
+
+    char = 'H'
+
+    def __call__(self, length, **kw):
+        s = self.settings
+        r = s.height / 2.0 / (1 - math.cos(math.radians(s.angle)))
+        l = r * math.sin(math.radians(s.angle))
+        p = 1 if self.bottom else -1
+
+        if self.bottom:
+            self.boxes.fingerHolesAt(0, s.height + self.settings.holedistance + 0.5 * self.boxes.thickness,
+                                     length, 0)
+
+        self.boxes.edge(s.width, tabs=1)
+        self.boxes.corner(p * s.angle, r)
+        self.boxes.corner(-p * s.angle, r)
+        self.boxes.edge(length - 1 * s.width - 2 * l)
+
+    def endwidth(self):
+        return self.settings.holedistance + self.settings.thickness
+
+
[docs]class NotesHolder(Boxes): + """Box for holding a stack of paper, coasters etc""" + + ui_group = "Box" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings, surroundingspaces=1) + self.addSettingsArgs(edges.StackableSettings) + self.buildArgParser(x=78, y=78, h=35, bottom_edge="s") + self.argparser.add_argument( + "--opening", action="store", type=float, default=40, + help="percent of front that's open") + + def render(self): + x, y, h = self.x, self.y, self.h + t = self.thickness + + o = max(0, min(self.opening, 100)) + sides = x * (1-o/100) / 2 + + b = self.edges.get(self.bottom_edge, self.edges["F"]) + if self.bottom_edge == "s": + b2 = HalfStackableEdge(self, self.edges["s"].settings, + self.edges["f"].settings) + else: + b2 = b + + with self.saved_context(): + self.rectangularWall(y, h, [b, "F", "e", "F"], + ignore_widths=[1, 6], move="right") + if self.opening == 0.0: + self.rectangularWall(x, h, [b, "f", "e", "f"], + ignore_widths=[1, 6], move="right") + else: + self.rectangularWall(sides, h, [b2, "e", "e", "f"], + ignore_widths=[1, 6], move="right") + self.rectangularWall(sides, h, [b2, "e", "e", "f"], + ignore_widths=[1, 6], move="right mirror") + + self.rectangularWall(x, h, [b, "F", "e", "F"], + ignore_widths=[1, 6], move="up only") + + with self.saved_context(): + self.rectangularWall(y, h, [b, "F", "e", "F"], ignore_widths=[1, 6], move="right") + self.rectangularWall(x, h, [b, "f", "e", "f"], ignore_widths=[1, 6], move="right") + self.rectangularWall(y, h, [b, "F", "e", "F"], move="up only") + + if self.bottom_edge != "e": + if self.opening == 0.0: + self.rectangularWall(x, y, ["f", "f", "f", "f"], move="up") + else: + self.rectangularWall(x, y, [USlotEdge(self, sides), "f", "f", "f"], move="up")
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/openbox.html b/html/_modules/boxes/generators/openbox.html new file mode 100644 index 0000000..8b041a8 --- /dev/null +++ b/html/_modules/boxes/generators/openbox.html @@ -0,0 +1,137 @@ + + + + + + + + boxes.generators.openbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.openbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class OpenBox(Boxes): + """Box with top and front open""" + + ui_group = "Box" + + def __init__(self): + Boxes.__init__(self) + self.buildArgParser("x", "y", "h", "outside") + self.argparser.add_argument( + "--edgetype", action="store", + type=ArgparseEdgeType("Fh"), choices=list("Fh"), + default="F", + help="edge type") + self.addSettingsArgs(edges.FingerJointSettings) + + def render(self): + x, y, h = self.x, self.y, self.h + t = self.thickness + + if self.outside: + x = self.adjustSize(x) + y = self.adjustSize(y, False) + h = self.adjustSize(h, False) + + e = self.edgetype + self.rectangularWall(x, h, [e, e, "e", e], move="right") + self.rectangularWall(y, h, [e, "e", "e", "f"], move="up") + self.rectangularWall(y, h, [e, "e", "e", "f"]) + self.rectangularWall(x, y, "efff", move="left")
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/organpipe.html b/html/_modules/boxes/generators/organpipe.html new file mode 100644 index 0000000..c93facb --- /dev/null +++ b/html/_modules/boxes/generators/organpipe.html @@ -0,0 +1,239 @@ + + + + + + + + boxes.generators.organpipe — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.organpipe

+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013-2018 Florian Festi
+#
+# Based on pipecalc by Christian F. Coors
+# https://github.com/ccoors/pipecalc
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from  math import *
+
+pitches = ['c', 'c#', 'd', 'd#', 'e', 'f', 'f#', 'g', 'g#', 'a', 'a#' ,'b']
+
+pressure_units = { 'Pa' : 1.0,
+                   'mBar' : 100.,
+                   'mmHg' : 133.322,
+                   'mmH2O' : 9.80665,
+}
+
+
[docs]class OrganPipe(Boxes): # Change class name! + """Rectangular organ pipe based on pipecalc""" + + ui_group = "Unstable" # see ./__init__.py for names + + def getFrequency(self, pitch, octave, base_freq=440): + steps = pitches.index(pitch) + (octave-4)*12 - 9 + return base_freq * 2**(steps/12.) + + def getRadius(self, pitch, octave, intonation): + steps = pitches.index(pitch) + (octave-2)*12 + intonation + return 0.5 * 0.15555 * 0.957458**steps + + def getAirSpeed(self, wind_pressure, air_density=1.2): + return (2.0 * (wind_pressure / air_density))**.5 + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings, finger=3.0, space=3.0, + surroundingspaces=1.0) + + """ + air_temperature: f64, +""" + # Add non default cli params if needed (see argparse std lib) + self.argparser.add_argument( + "--pitch", action="store", type=str, default="c", + choices=pitches, + help="pitch") + self.argparser.add_argument( + "--octave", action="store", type=int, default=2, + help="Octave in International Pitch Notation (2 == C)") + self.argparser.add_argument( + "--intonation", action="store", type=float, default=2.0, + help="Intonation Number. 2 for max. efficiency, 3 max.") + self.argparser.add_argument( + "--mouthratio", action="store", type=float, default=0.25, + help="mouth to circumference ratio (0.1 to 0.45). Determines the width to depth ratio") + self.argparser.add_argument( + "--cutup", action="store", type=float, default=0.3, + help="Cutup to mouth ratio") + self.argparser.add_argument( + "--mensur", action="store", type=int, default=0, + help=u"Distance in halftones in the Normalmensur by Töpfer") + self.argparser.add_argument( + "--windpressure", action="store", type=float, default=588.4, + help="uses unit selected below") + self.argparser.add_argument( + "--windpressure_units", action="store", type=str, default='Pa', + choices=pressure_units.keys(), + help="in Pa") + self.argparser.add_argument( + "--stopped", action="store", type=boolarg, default=False, + help="pipe is closed at the top") + + + def render(self): + t = self.thickness + f = self.getFrequency(self.pitch, self.octave, 440) + + self.windpressure *= pressure_units.get(self.windpressure_units, 1.0) + + speed_of_sound = 343.6 # XXX util::speed_of_sound(self.air_temperature); // in m/s + air_density = 1.2 + air_speed = self.getAirSpeed(self.windpressure, air_density) + + i = self.intonation; + radius = self.getRadius(self.pitch, self.octave, i) * 1000 + cross_section = pi * radius**2 + circumference = pi * radius * 2.0 + mouth_width = circumference * self.mouthratio + mouth_height = mouth_width * self.cutup + mouth_area = mouth_height * mouth_width + pipe_depth = cross_section / mouth_width + base_length = max(mouth_width, pipe_depth) + + jet_thickness = (f**2 * i**2 * (.01 * mouth_height)**3) / air_speed**2 + sound_power = (0.001 * pi * (air_density / speed_of_sound) * f**2 + * (1.7 * (jet_thickness * speed_of_sound * f * mouth_area * mouth_area**.5)**.5)**2) + + air_consumption_rate = air_speed * mouth_width * jet_thickness * 1E6; + + wavelength = speed_of_sound / f * 1000; + + if self.stopped: + theoretical_resonator_length = wavelength / 4.0 + resonator_length = (-0.73 * (f * cross_section *1E-6 - 0.342466 * speed_of_sound * mouth_area**.5 * 1E-3) + / (f * mouth_area**.5 * 1E-3)) + else: + theoretical_resonator_length = wavelength / 2.0 + resonator_length = (-0.73 * (f * cross_section * 1E-6 + 0.465753 * f * mouth_area**.5 * cross_section**.5 * 1E-6 - 0.684932 * speed_of_sound * mouth_area**.5 * 1E-3) + / (f * mouth_area**.5 * 1E-3)) * 1E3 + air_hole_diameter = 2.0 * ((mouth_width * jet_thickness * 10.0)**.5 / pi) + + total_length = resonator_length + base_length + + + e = ["f", "e", + edges.CompoundEdge(self, "fef", (resonator_length - mouth_height - 10*t, mouth_height + 10*t, base_length)), "f"] + + self.rectangularWall(total_length, pipe_depth, e, callback=[ + lambda: self.fingerHolesAt(base_length-0.5*t, 0, pipe_depth-jet_thickness)], + move="up") + self.rectangularWall(total_length, pipe_depth, e, callback=[ + lambda: self.fingerHolesAt(base_length-0.5*t, 0, pipe_depth-jet_thickness)], + move="up") + self.rectangularWall(total_length, mouth_width, "FeFF", callback=[ + lambda: self.fingerHolesAt(base_length-0.5*t, 0, mouth_width)], + move="up") + e = [edges.CompoundEdge(self, "EF", (t*10, resonator_length - mouth_height - t*10)), 'e', + edges.CompoundEdge(self, "FE", (resonator_length - mouth_height - t*10, t*10)), 'e'] + self.rectangularWall(resonator_length - mouth_height, mouth_width, e, move="up") + self.rectangularWall(base_length, mouth_width, "FeFF", move="right") + self.rectangularWall(mouth_width, pipe_depth, "fFfF", callback=[ + lambda:self.hole(mouth_width/2, pipe_depth/2, d=air_hole_diameter)], move="right") + self.rectangularWall(mouth_width, pipe_depth - jet_thickness, "ffef", move="right")
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/ottobody.html b/html/_modules/boxes/generators/ottobody.html new file mode 100644 index 0000000..06eb467 --- /dev/null +++ b/html/_modules/boxes/generators/ottobody.html @@ -0,0 +1,257 @@ + + + + + + + + boxes.generators.ottobody — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.ottobody

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+try:
+    from gettext import gettext as _
+except ImportError:
+    def _(message):
+        return message
+
+from boxes import *
+
+
+
[docs]class OttoBody(Boxes): + """Otto LC - a laser cut chassis for Otto DIY - body""" + + ui_group = "Misc" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.addSettingsArgs(edges.ChestHingeSettings) + + + def bottomCB(self): + self.hole(6, self.y/2, 6) + self.hole(6, self.y/2-6, 3) + self.hole(self.x-6, self.y/2, 6) + self.hole(self.x-6, self.y/2-6, 3) + + #self.rectangularHole(20, self.y/2, 4, 30, 1.5) + #self.rectangularHole(self.x-20, self.y/2, 4, 30, 1.5) + self.rectangularHole(self.x/2, self.y/2, 10, 5, 1.5) + + # switch + self.rectangularHole(self.x-7, self.y-2.8, 7, 4) + + self.moveTo(0, self.y-12) + self.hexHolesCircle(12, HexHolesSettings( + self, diameter=2, distance=2, style='circle')) + + def leftBottomCB(self): + self.hole(7, self.y-7, 6) + + self.hole(6, self.y/2+9, 0.9) + self.rectangularHole(6, self.y/2-5.5, 12, 23) + self.hole(6, self.y/2-20, 0.9) + + def rightBottomCB(self): + self.hole(7, self.y-5, 2) + + self.hole(8, self.y/2+9, 0.9) + self.rectangularHole(8, self.y/2-5.5, 12, 23) + self.hole(8, self.y/2-20, 0.9) + + def eyeCB(self): + self.hole(self.x/2+13,self.hl/2, 8) + self.hole(self.x/2-13,self.hl/2, 8) + + def frontCB(self): + t = self.thickness + self.rectangularHole(0.5*t, 2+t, t, 2.5) + self.rectangularHole(self.x-0.5*t, 2+t, t, 2.5) + + def IOCB(self): + self.rectangularHole(26, 18, 12, 10) + # self.rectangularHole(42.2, 10.2, 9.5, 11.5) + + def buttonCB(self): + px, py = 7.5, 7.5 + + self.rectangularHole(px, py-2.25, 5.2, 2.5) + self.rectangularHole(px, py+2.25, 5.2, 2.5) + + def PCB_Clip(self, x , y, move=None): + + if self.move(x+4, y, move, True): + return + self.moveTo(1.5) + self.polyline(x-1.5, 90, (y, 2), 90, x, 85, (y-2-4, 2), -30, 2, 120, 1, -90, 2, (180, 1.), y-7, -175, y-5) + + self.move(x+4, y, move) + + def PCB_Clamp(self, w, s, h, move=None): + t = self. thickness + f = 2**0.5 + + if self.move(w+4, h+8+t, move, True): + return + self.polyline(w, 90, s, -90, 1, (90, 1), (h-s-1, 2), 90, w-2, 90, + h-8, (-180, 1), h-8+3*t, 135, f*(4), 90, f*2, -45, + (h+t, 2)) + self.move(w+4, h+8+t, move) + + def render(self): + + self.x = x = 60. + self.y = y = 60. + self.h = h = 35. + self.hl = hl = 30. + + t = self.thickness + + hx = self.edges["O"].startwidth() + hx2 = self.edges["P"].startwidth() + + e1 = edges.CompoundEdge(self, "Fe", (h-hx, hx)) + e2 = edges.CompoundEdge(self, "eF", (hx, h-hx)) + e_back = ("F", e1, "e", e2) + + # sides + self.moveTo(hx) + self.rectangularWall(x, h-hx, "FfOf", ignore_widths=[2], move="up", label=_("Left bottom side")) + self.rectangularWall(x, hl-hx2, "pfFf", ignore_widths=[1], move="up", label=_("Left top side")) + self.moveTo(-hx) + self.rectangularWall(x, h-hx, "Ffof", ignore_widths=[5], callback=[ + lambda: self.rectangularHole(y-7.5, h-4-7.5, 6.2, 7.)], + move="up", label=_("Right bottom side")) + self.rectangularWall(x, hl-hx2, "PfFf", ignore_widths=[6], + callback=[None, None, self.IOCB], move="up", + label=_("Right top side")) + + # lower walls + self.rectangularWall(y, h, "FFeF", callback=[ + None, None, self.frontCB], move="up", + label=_("Lower front")) + self.rectangularWall(y, h, e_back, move="up", + label=_("Lower back")) + + # upper walls + self.rectangularWall(y, hl, "FFeF", callback=[self.eyeCB], move="up", label=_("Upper front")) + self.rectangularWall(y, hl-hx2, "FFqF", move="up", label=_("Upper back")) + + # top + self.rectangularWall(x, y, "ffff", move="up", label=_("Top")) + # bottom + self.rectangularWall(x, y, "ffff", callback=[self.bottomCB], move="up", label=_("Bottom")) + # PCB mounts + with self.saved_context(): + self.PCB_Clamp(y-53.5, 4.5, hl, move="right") + self.PCB_Clamp(y-50, 4.5, hl, move="right") + self.PCB_Clip(3.5, hl, move="right") + self.rectangularWall(15, 15, callback=[self.buttonCB]) + self.PCB_Clamp(y-53.5, 4.5, hl, move="up only") + # servo mounts + self.moveTo(0, 50) + self.rectangularWall(y, 14, callback=[None, None, None, + self.leftBottomCB], move="up", + label=_("Servo mount")) + self.rectangularWall(y-5.6, 14, callback=[ + None, None, None, self.rightBottomCB], move="up", + label=_("Servo mount"))
+ + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/ottolegs.html b/html/_modules/boxes/generators/ottolegs.html new file mode 100644 index 0000000..ec7c289 --- /dev/null +++ b/html/_modules/boxes/generators/ottolegs.html @@ -0,0 +1,241 @@ + + + + + + + + boxes.generators.ottolegs — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.ottolegs

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes import edges
+
+class LegEdge(edges.BaseEdge):
+
+    def __call__(self, l, **kw):
+        d0 = (l - 12.0) /2
+        self.hole(l/2, 6, 3.0)
+        self.polyline(d0, 90, 0, (-180, 6), 0, 90, d0)
+
+
[docs]class OttoLegs(Boxes): + """Otto LC - a laser cut chassis for Otto DIY - legs""" + + ui_group = "Misc" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings, finger=1.0, space=1.0, + surroundingspaces=1.0) + self.argparser.add_argument( + "--anklebolt1", action="store", type=float, default=3.0, + help="diameter for hole for ankle bolts - foot side") + self.argparser.add_argument( + "--anklebolt2", action="store", type=float, default=2.6, + help="diameter for hole for ankle bolts - leg side") + self.argparser.add_argument( + "--length", action="store", type=float, default=34.0, + help="length of legs (34mm min)") + + def foot(self, x, y, ly, l, r=5., move=None): + if self.move(x, y, move, True): + return + + t = self.thickness + w = ly + 5.5 + 2 * t + self.fingerHolesAt(x/2 - w/2, 0, l, 90) + self.fingerHolesAt(x/2 + w/2, 0, l, 90) + self.moveTo(r, 0) + + for l in (x, y, x, y): + self.polyline((l - 2*r, 2), 45, r*2**0.5, 45) + + self.move(x, y, move) + + def ankles(self, x, h, edge="f", callback=None, move=None): + + f = 0.5 + tw = x + th = 2 * h + self.thickness + + if self.move(tw, th, move, True): + return + + self.moveTo(0, self.thickness) + for i in range(2): + self.cc(callback, 0) + self.edges[edge](x) + self.polyline(0, 90) + self.cc(callback, 1) + self.polyline((h, 2), 90, (f*x, 1), 45, ((2**0.5)*(1-f)*x, 1), 45, + (h-(1-f)*x, 1), 90) + self.moveTo(tw, th, 180) + self.ctx.stroke() + self.move(tw, th, move) + + def ankle1(self): + # from vertical edge + self.hole(15, 10, 3.45) # 3.45 for servo arm, 2.3 for knob + + def servoring(self, move=""): + if self.move(20, 20, move, True): + return + self.moveTo(10, 10, 90) + self.moveTo(3.45, 0, -90) + self.polyline(0, (-264, 3.45), 0, 36, 6.55, 108, 0, (330, 9.0, 4), 0, 108, 6.55) + self.move(20, 20, move) + + + def ankle2(self): + # from vertical edge + self.hole(15, 10, self.anklebolt1/2) + + def servoHole(self): + self.hole(6, 6, 11.6/2) + self.hole(6, 12, 5.5/2) + + def render(self): + # adjust to the variables you want in the local scope + t = self.thickness + + ws = 25 + lx, ly, lh = 12.4, 23.5, max(self.length, ws+6+t) + + self.ctx.save() + # Legs + + c1 = edges.CompoundEdge(self, "FE", (ly-7.0, 7.0)) + c2 = edges.CompoundEdge(self, "EF", (7.0, lh-7.0)) + e = [c1, c2, "F", "F"] + + for i in range(2): + # front + self.rectangularWall(lx, lh-7., [LegEdge(self, None), "f", "F", "f"], callback=[None, lambda:self.fingerHolesAt(ws-7., 0, lx)], move="right") + # back + self.rectangularWall(lx, lh, "FfFf", callback=[ + lambda:self.hole(lx/2, 7, self.anklebolt2/2)], move="right") + # sides + self.rectangularWall(ly, lh, e, callback=[None, + lambda:self.fingerHolesAt(ws, 7.0, ly-7.0-3.0)], move="right") + self.rectangularWall(ly, lh, e, callback=[ + lambda:self.rectangularHole(ly/2, ws+3+0.5*t, 12, 6, 3), + lambda:self.fingerHolesAt(ws, 7.0, ly-7.0-3.0)], move="right") + + # top + self.partsMatrix(2, 1, "right", self.rectangularWall, ly, lx, "ffff", + callback=[None, lambda: self.hole(lx/2, ly/2, 2.3)]) + self.partsMatrix(2, 1, "right", self.rectangularWall, lx, ly, "eeee", callback=[lambda: self.hole(lx/2, ly/2, 1.5)]) + # hold servo at the front + self.partsMatrix(2, 1, "right", self.rectangularWall, 4.6, lx, "efee") + # bottom + self.partsMatrix(2, 1, "right", self.rectangularWall, lx, ly-7.0, "efff") + # hold servo inside + self.partsMatrix(2, 1, "right", self.rectangularWall, lx, ly-7.0-3.0, "efef") + + self.ctx.restore() + self.rectangularWall(lx, lh, "ffff", move="up only") + + # feet + self.foot(60, 40, ly, 30, move="right") + self.foot(60, 40, ly, 30, move="right") + self.ankles(30, 25, callback=[None, self.ankle1], move="right") + self.ankles(30, 25, callback=[None, self.ankle2], move="right") + self.partsMatrix(2, 2, "right", self.servoring)
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/ottosoles.html b/html/_modules/boxes/generators/ottosoles.html new file mode 100644 index 0000000..884fa1f --- /dev/null +++ b/html/_modules/boxes/generators/ottosoles.html @@ -0,0 +1,160 @@ + + + + + + + + boxes.generators.ottosoles — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.ottosoles

+#!/usr/bin/env python3
+# Copyright (C) 2013-2018 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class OttoSoles(Boxes): + """Foam soles for the OttO bot""" + + ui_group = "Misc" + + def __init__(self): + Boxes.__init__(self) + + self.buildArgParser(x=58., y=38.) + self.argparser.add_argument( + "--width", action="store", type=float, default=4., + help="width of sole stripe") + self.argparser.add_argument( + "--chamfer", action="store", type=float, default=5., + help="chamfer at the corners") + self.argparser.add_argument( + "--num", action="store", type=int, default=2, + help="number of soles") + + + def render(self): + x, y = self.x, self.y + c = self.chamfer + c2 = c * 2**0.5 + w = min(self.width, c2 / 2. / math.tan(math.radians(22.5))) + w = self.width + w2 = w * 2**0.5 - c2 / 2 + d = w * math.tan(math.radians(22.5)) + + + self.edges["d"].settings.setValues(w, size=0.4, depth=0.3, + radius=0.05) + + self.moveTo(0, y, -90) + + for i in range(self.num*2): + if c2 >= 2 * d: + self.polyline((c2, 1), 45, (y-2*c, 1), 45, c2/2., 90) + self.edges["d"](w) + self.polyline(0, 90, c2/2-d, -45, (y-2*c-2*d, 1), -45, + (c2-2*d, 1), -45, + (x-2*c-2*d, 1), -45, c2/2-d, 90) + self.edges["D"](w) + self.polyline(0, 90, c2/2., 45, (x-2*c, 1), 45) + self.moveTo(0, w + c2/2. + 2*2**0.5*self.burn) + else: + self.polyline((c2, 1), 45, (y-2*c, 1), 45, c2/2., 90) + self.edges["d"](w2) + self.polyline(0, 45, (y-2*w, 1), -90, (x-2*w, 1), 45) + self.edges["D"](w2) + self.polyline(0, 90, c2/2., 45, (x-2*c, 3), 45) + self.moveTo(0, w * 2**0.5 + 2*2**0.5*self.burn)
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/paintbox.html b/html/_modules/boxes/generators/paintbox.html new file mode 100644 index 0000000..d627108 --- /dev/null +++ b/html/_modules/boxes/generators/paintbox.html @@ -0,0 +1,225 @@ + + + + + + + + boxes.generators.paintbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.paintbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import Boxes, edges, boolarg
+
+
+
[docs]class PaintStorage(Boxes): + """Stackable storage for hobby paint or other things""" + + webinterface = True + ui_group = "Shelf" # see ./__init__.py for names + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings) + self.addSettingsArgs(edges.StackableSettings) + + self.buildArgParser(x=100, y=300) + + self.argparser.add_argument( + "--canheight", action="store", type=int, default=50, + help="Height of the paintcans") + self.argparser.add_argument( + "--candiameter", action="store", type=int, default=30, + help="Diameter of the paintcans") + self.argparser.add_argument( + "--minspace", action="store", type=int, default=10, + help="Minimum space between the paintcans") + self.argparser.add_argument( + "--hexpattern", action="store", type=boolarg, default=False, + help="Use hexagonal arrangement for the holes instead of orthogonal") + self.argparser.add_argument( + "--drawer", action="store", type=boolarg, default=False, + help="Create a stackable drawer instead") + + def paintholes(self): + "Place holes for the paintcans evenly" + + if self.hexpattern: + self.moveTo(self.minspace/2, self.minspace/2) + settings = self.hexHolesSettings + settings.diameter = self.candiameter + settings.distance = self.minspace + settings.style = 'circle' + self.hexHolesRectangle(self.y - 1*self.minspace, + self.x - 1*self.minspace, + settings) + return + n_x = int(self.x / (self.candiameter+self.minspace)) + n_y = int(self.y / (self.candiameter+self.minspace)) + + if n_x <= 0 or n_y <= 0: + return + + spacing_x = (self.x - n_x*self.candiameter)/n_x + spacing_y = (self.y - n_y*self.candiameter)/n_y + for i in range(n_y): + for j in range(n_x): + self.hole(i * (self.candiameter+spacing_y) + (self.candiameter+spacing_y)/2, + j * (self.candiameter+spacing_x) + (self.candiameter+spacing_x)/2, + self.candiameter/2) + + def render(self): + # adjust to the variables you want in the local scope + x, y = self.x, self.y + t = self.thickness + + stack = self.edges['s'].settings + h = self.canheight - stack.height - stack.holedistance + t + + hx = 1/2.*x + hh = h/4. + hr = min(hx, hh) / 2 + + if not self.drawer: + wall_keys = "EsES" + wall_callbacks = [ + lambda: self.rectangularHole(h / 3, (x / 2.0) - t, hh, hx, r=hr), + lambda: self.fingerHolesAt(0, self.canheight / 3, x, 0), + ] + bottom_keys = "EfEf" + else: + wall_keys = "FsFS" + wall_callbacks = [ + lambda: self.rectangularHole(h / 3, (x / 2.0) - t, hh, hx, r=hr) + ] + bottom_keys = "FfFf" + + + # Walls + self.rectangularWall( + h, x - 2 * t, wall_keys, + ignore_widths=[1, 2, 5, 6], + callback=wall_callbacks, move="up", + ) + self.rectangularWall( + h, x - 2 * t, wall_keys, + ignore_widths=[1, 2, 5, 6], + callback=wall_callbacks, move="right" + ) + + # Plates + self.rectangularWall( + 0.8 * stack.height + stack.holedistance, x, "eeee", move="" + ) + self.rectangularWall( + 0.8 * stack.height + stack.holedistance, x, "eeee", move="down right" + ) + + # Bottom + self.rectangularWall( + y, x-2*t, bottom_keys, ignore_widths=[1, 2, 5, 6], move="up" + ) + + if not self.drawer: + # Top + self.rectangularWall(y, x, "efef", callback=[self.paintholes], move="up") + else: + # Sides + self.rectangularWall(y, h, "efff", move="up") + self.rectangularWall(y, h, "efff", move="up")
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/paperbox.html b/html/_modules/boxes/generators/paperbox.html new file mode 100644 index 0000000..a8f1300 --- /dev/null +++ b/html/_modules/boxes/generators/paperbox.html @@ -0,0 +1,367 @@ + + + + + + + + boxes.generators.paperbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.paperbox

+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2020 Guillaume Collic
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import math
+from boxes import Boxes
+
+
+
[docs]class PaperBox(Boxes): + """ + Box made of paper, with lid. + """ + + ui_group = "Misc" + + description = """ +This box is made of paper. + +There is marks in the "outside leftover paper" to help see where to fold +(cutting with tabs helps use them). The cut is very precise, and could be too tight if misaligned when glued. A plywood box (such as a simple TypeTray) of the same size is a great guide during folding and glueing. Just fold the box against it. Accurate quick and easy. + +A paper creaser (or bone folder) is also useful. +""" + + def __init__(self): + Boxes.__init__(self) + self.buildArgParser("x", "y", "h") + + self.argparser.add_argument( + "--design", + action="store", + type=str, + default="automatic", + choices=("automatic", "widebox", "tuckbox"), + help="different design for paper consumption optimization. The tuckbox also has locking cut for its lid.", + ) + + self.argparser.add_argument( + "--lid_heigth", + type=float, + default=15, + help="Height of the lid (part which goes inside the box)", + ) + self.argparser.add_argument( + "--lid_radius", + type=float, + default=7, + help="Angle, in radius, of the round corner of the lid", + ) + self.argparser.add_argument( + "--lid_sides", + type=float, + default=20, + help="Width of the two sides upon which goes the lid", + ) + self.argparser.add_argument( + "--margin", + type=float, + default=0, + help="Margin for the glued sides", + ) + self.argparser.add_argument( + "--mark_length", + type=float, + default=1.5, + help="Length of the folding outside mark", + ) + self.argparser.add_argument( + "--tab_angle_rad", + type=float, + default=math.atan(2 / 25), + help="Angle (in radian) of the sides which are to be glued inside the box", + ) + + self.argparser.add_argument( + "--finger_hole_diameter", + type=float, + default=15, + help="Diameter of the hole to help catch the lid", + ) + + def render(self): + if self.design == "automatic": + self.design = "tuckbox" if self.h > self.y else "widebox" + + path = ( + self.tuckbox(self.x, self.y, self.h) + if self.design == "tuckbox" + else self.widebox(self.x, self.y, self.h) + ) + + self.polyline(*path) + + def tuckbox(self, width, length, height): + lid_cut_length = min(10, length / 2, width / 5) + half_side = ( + self.mark(self.mark_length) + + [ + 0, + 90, + ] + + self.ear_description(length, lid_cut_length) + + [ + 0, + -90, + length, + 0, + ] + + self.lid_cut(lid_cut_length) + + self.lid(width - 2 * self.thickness) + + [0] + + self.lid_cut(lid_cut_length, reverse=True) + + [ + length, + -90, + ] + + self.ear_description(length, lid_cut_length, reverse=True) + + self.mark(self.mark_length) + ) + return ( + [height, 0] + + half_side + + self.side_with_finger_hole(width, self.finger_hole_diameter) + + self.mark(self.mark_length) + + [ + 0, + 90, + ] + + self.tab_description(length - self.margin - self.thickness, height) + + [ + 0, + 90, + ] + + self.mark(self.mark_length) + + [width] + + list(reversed(half_side)) + ) + + def widebox(self, width, length, height): + half_side = ( + self.mark(self.mark_length) + + [ + 0, + 90, + ] + + self.tab_description(length / 2 - self.margin, height) + + [ + 0, + -90, + height, + 0, + ] + + self.mark(self.mark_length) + + [ + 0, + 90, + ] + + self.tab_description(self.lid_sides, length) + + [ + 0, + 90, + ] + + self.mark(self.mark_length) + + [ + height, + -90, + ] + + self.tab_description(length / 2 - self.margin, height) + + [ + length, + 0, + ] + + self.mark(self.mark_length) + ) + return ( + self.side_with_finger_hole(width, self.finger_hole_diameter) + + half_side + + self.lid(width) + + list(reversed(half_side)) + ) + + def lid(self, width): + return [ + self.lid_heigth - self.lid_radius, + (90, self.lid_radius), + width - 2 * self.lid_radius, + (90, self.lid_radius), + self.lid_heigth - self.lid_radius, + ] + + def mark(self, length): + if length == 0: + return [] + return [ + 0, + -90, + length, + 180, + length, + -90, + ] + + def lid_cut(self, length, reverse=False): + path = [ + 90, + length + self.thickness, + -180, + length, + 90, + ] + + return [0] + (list(reversed(path)) if reverse else path) + + def side_with_finger_hole(self, width, finger_hole_diameter): + half_width = (width - finger_hole_diameter) / 2 + + return [ + half_width, + 90, + 0, + (-180, finger_hole_diameter / 2), + 0, + 90, + half_width, + 0, + ] + + def tab_description(self, height, width): + deg = math.degrees(self.tab_angle_rad) + side = height / math.cos(self.tab_angle_rad) + end_width = width - 2 * height * math.tan(self.tab_angle_rad) + return [ + 0, + deg - 90, + side, + 90 - deg, + end_width, + 90 - deg, + side, + deg - 90, + ] + + def ear_description(self, length, lid_cut_length, reverse=False): + ear_depth = max(lid_cut_length, self.lid_heigth) + radius = min(self.lid_radius, ear_depth - lid_cut_length) + start_margin = self.thickness + end_margin = 2 * self.burn + path = [ + start_margin, + -90, + lid_cut_length, + 90, + 0, + (-90, radius), + 0, + 90, + length - radius - start_margin - end_margin, + 90, + ear_depth, + -90, + end_margin, + ] + + return (list(reversed(path)) if reverse else path) + [0]
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/phoneholder.html b/html/_modules/boxes/generators/phoneholder.html new file mode 100644 index 0000000..6b8e0b8 --- /dev/null +++ b/html/_modules/boxes/generators/phoneholder.html @@ -0,0 +1,396 @@ + + + + + + + + boxes.generators.phoneholder — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.phoneholder

+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2021 Guillaume Collic
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import math
+from functools import partial
+from boxes import Boxes, edges
+
+
+
[docs]class PhoneHolder(Boxes): + """ + Smartphone desk holder + """ + + ui_group = "Misc" + + description = """ + This phone stand holds your phone between two tabs, with access to its + bottom, in order to connect a charger, headphones, and also not to obstruct + the mic. + + Default values are currently based on Galaxy S7. +""" + + def __init__(self): + Boxes.__init__(self) + self.argparser.add_argument( + "--phone_height", + type=float, + default=142, + help="Height of the phone.", + ) + self.argparser.add_argument( + "--phone_width", + type=float, + default=73, + help="Width of the phone.", + ) + self.argparser.add_argument( + "--phone_depth", + type=float, + default=11, + help=( + "Depth of the phone. Used by the bottom support holding the " + "phone, and the side tabs depth as well. Should be at least " + "your material thickness for assembly reasons." + ), + ) + self.argparser.add_argument( + "--angle", + type=float, + default=25, + help="angle at which the phone stands, in degrees. 0° is vertical.", + ) + self.argparser.add_argument( + "--bottom_margin", + type=float, + default=30, + help="Height of the support below the phone", + ) + self.argparser.add_argument( + "--tab_size", + type=float, + default=76, + help="Length of the tabs holding the phone", + ) + self.argparser.add_argument( + "--bottom_support_spacing", + type=float, + default=16, + help=( + "Spacing between the two bottom support. Choose a value big " + "enough for the charging cable, without getting in the way of " + "other ports." + ), + ) + + self.addSettingsArgs(edges.FingerJointSettings) + + def render(self): + self.h = self.phone_height + self.bottom_margin + tab_start = self.bottom_margin + tab_length = self.tab_size + tab_depth = self.phone_depth + support_depth = self.phone_depth + support_spacing = self.bottom_support_spacing + rad = math.radians(self.angle) + self.stand_depth = self.h * math.sin(rad) + self.stand_height = self.h * math.cos(rad) + + self.render_front_plate(tab_start, tab_length, support_spacing, move="right") + + self.render_back_plate(move="right") + + self.render_side_plate(tab_start, tab_length, tab_depth, move="right") + + for move in ["right mirror", "right"]: + self.render_bottom_support(tab_start, support_depth, tab_length, move=move) + + def render_front_plate( + self, + tab_start, + tab_length, + support_spacing, + support_fingers_length=None, + move="right", + ): + if not support_fingers_length: + support_fingers_length = tab_length + + be = BottomEdge(self, tab_start, support_spacing) + se1 = SideEdge(self, tab_start, tab_length) + se2 = SideEdge(self, tab_start, tab_length, reverse=True) + self.rectangularWall( + self.phone_width, + self.h, + [be, se1, "e", se2], + move=move, + callback=[ + partial( + lambda: self.front_plate_holes( + tab_start, support_fingers_length, support_spacing + ) + ) + ], + ) + + def render_back_plate( + self, + move="right", + ): + be = BottomEdge(self, 0, 0) + self.rectangularWall( + self.phone_width, + self.stand_height, + [be, "F", "e", "F"], + move=move, + ) + + def front_plate_holes( + self, support_start_height, support_fingers_length, support_spacing + ): + margin = (self.phone_width - support_spacing - self.thickness) / 2 + self.fingerHolesAt( + margin, + support_start_height, + support_fingers_length, + ) + self.fingerHolesAt( + self.phone_width - margin, + support_start_height, + support_fingers_length, + ) + + def render_side_plate(self, tab_start, tab_length, tab_depth, move): + te = TabbedEdge(self, tab_start, tab_length, tab_depth, reverse=True) + self.rectangularTriangle( + self.stand_depth, + self.stand_height, + ["e", "f", te], + move=move, + num=2, + ) + + def render_bottom_support( + self, support_start_height, support_depth, support_fingers_length, move="right" + ): + full_height = support_start_height + support_fingers_length + rad = math.radians(self.angle) + floor_length = full_height * math.sin(rad) + angled_height = full_height * math.cos(rad) + bottom_radius = min(support_start_height, 3 * self.thickness + support_depth) + smaller_radius = 0.5 + support_hook_height = 5 + full_width = floor_length + (support_depth + 3 * self.thickness) * math.cos(rad) + if self.move(full_width, angled_height, move, True): + return + + self.polyline( + floor_length, + self.angle, + 3 * self.thickness + support_depth - bottom_radius, + (90, bottom_radius), + support_hook_height + support_start_height - bottom_radius, + (180, self.thickness), + support_hook_height - smaller_radius, + (-90, smaller_radius), + self.thickness + support_depth - smaller_radius, + -90, + ) + self.edges["f"](support_fingers_length) + self.polyline( + 0, + 180 - self.angle, + angled_height, + 90, + ) + # Move for next piece + self.move(full_width, angled_height, move)
+ + +class BottomEdge(edges.BaseEdge): + def __init__(self, boxes, support_start_height, support_spacing): + super().__init__(boxes, None) + self.support_start_height = support_start_height + self.support_spacing = support_spacing + + def __call__(self, length, **kw): + cable_hole_radius = 2.5 + self.support_spacing = max(self.support_spacing, 2 * cable_hole_radius) + side = (length - self.support_spacing - 2 * self.thickness) / 2 + + half = [ + side, + 90, + self.support_start_height, + -90, + self.thickness, + -90, + self.support_start_height, + 90, + self.support_spacing / 2 - cable_hole_radius, + 90, + 2 * cable_hole_radius, + ] + path = half + [(-180, cable_hole_radius)] + list(reversed(half)) + self.polyline(*path) + + +class SideEdge(edges.BaseEdge): + def __init__(self, boxes, tab_start, tab_length, reverse=False): + super().__init__(boxes, None) + self.tab_start = tab_start + self.tab_length = tab_length + self.reverse = reverse + + def __call__(self, length, **kw): + tab_start = self.tab_start + tab_end = length - self.tab_start - self.tab_length + + if self.reverse: + tab_start, tab_end = tab_end, tab_start + + self.edges["F"](tab_start) + self.polyline( + 0, + 90, + self.thickness, + -90, + ) + self.edges["f"](self.tab_length) + self.polyline(0, -90, self.thickness, 90) + self.edges["F"](tab_end) + + def startwidth(self): + return self.boxes.thickness + + +class TabbedEdge(edges.BaseEdge): + def __init__(self, boxes, tab_start, tab_length, tab_depth, reverse=False): + super().__init__(boxes, None) + self.tab_start = tab_start + self.tab_length = tab_length + self.tab_depth = tab_depth + self.reverse = reverse + + def __call__(self, length, **kw): + tab_start = self.tab_start + tab_end = length - self.tab_start - self.tab_length + + if self.reverse: + tab_start, tab_end = tab_end, tab_start + + self.edges["f"](tab_start) + + self.ctx.save() + self.fingerHolesAt(0, -self.thickness / 2, self.tab_length, 0) + self.ctx.restore() + + self.polyline( + 0, + -90, + self.thickness, + (90, self.tab_depth), + self.tab_length - 2 * self.tab_depth, + (90, self.tab_depth), + self.thickness, + -90, + ) + self.edges["f"](tab_end) + + def margin(self): + return self.tab_depth + self.thickness +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/planetary.html b/html/_modules/boxes/generators/planetary.html new file mode 100644 index 0000000..2016dfe --- /dev/null +++ b/html/_modules/boxes/generators/planetary.html @@ -0,0 +1,197 @@ + + + + + + + + boxes.generators.planetary — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.planetary

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+import math
+
+
+
[docs]class Planetary(Boxes): + + """Planetary Gear with possibly multiple identical stages""" + + ui_group = "Part" + + def __init__(self): + Boxes.__init__(self) + self.argparser.add_argument( + "--sunteeth", action="store", type=int, default=8, + help="number of teeth on sun gear") + self.argparser.add_argument( + "--planetteeth", action="store", type=int, default=20, + help="number of teeth on planets") + self.argparser.add_argument( + "--maxplanets", action="store", type=int, default=0, + help="limit the number of planets (0 for as much as fit)") + self.argparser.add_argument( + "--deltateeth", action="store", type=int, default=0, + help="enable secondary ring with given delta to the ring gear") + self.argparser.add_argument( + "--modulus", action="store", type=float, default=3, + help="modulus of the theeth in mm") + self.argparser.add_argument( + "--shaft", action="store", type=float, default=6., + help="diameter of the shaft") + # self.argparser.add_argument( + # "--stages", action="store", type=int, default=4, + # help="number of stages in the gear reduction") + + def render(self): + + ringteeth = self.sunteeth + 2 * self.planetteeth + spoke_width = 3 * self.shaft + + pitch1, size1, xxx = self.gears.sizes(teeth=self.sunteeth, + dimension=self.modulus) + pitch2, size2, xxx = self.gears.sizes(teeth=self.planetteeth, + dimension=self.modulus) + pitch3, size3, xxx = self.gears.sizes( + teeth=ringteeth, internal_ring=True, spoke_width=spoke_width, + dimension=self.modulus) + + t = self.thickness + planets = int(math.pi / (math.asin(float(self.planetteeth + 2) / (self.planetteeth + self.sunteeth)))) + + if self.maxplanets: + planets = min(self.maxplanets, planets) + + # Make sure the teeth mash + ta = self.sunteeth + ringteeth + # There are sunteeth+ringteeth mashing positions for the planets + if ta % planets: + planetpositions = [round(i * ta / planets) * 360 / ta for i in range(planets)] + else: + planetpositions = planets + + # XXX make configurable? + profile_shift = 20 + pressure_angle = 20 + self.parts.disc(size3, callback=lambda: self.hole(0, 0, self.shaft / 2), move="up") + self.gears(teeth=ringteeth, dimension=self.modulus, + angle=pressure_angle, internal_ring=True, + spoke_width=spoke_width, mount_hole=self.shaft, + profile_shift=profile_shift, move="up") + self.gears.gearCarrier(pitch1 + pitch2, spoke_width, planetpositions, + 2 * spoke_width, self.shaft / 2, move="up") + self.gears(teeth=self.sunteeth, dimension=self.modulus, + angle=pressure_angle, + mount_hole=self.shaft, profile_shift=profile_shift, move="up") + numplanets = planets + + if self.deltateeth: + numplanets += planets + deltamodulus = self.modulus * ringteeth / (ringteeth - self.deltateeth) + self.gears(teeth=ringteeth - self.deltateeth, dimension=deltamodulus, + angle=pressure_angle, internal_ring=True, + spoke_width=spoke_width, mount_hole=self.shaft, + profile_shift=profile_shift, move="up") + + for i in range(numplanets): + self.gears(teeth=self.planetteeth, dimension=self.modulus, + angle=pressure_angle, + mount_hole=self.shaft, profile_shift=profile_shift, move="up")
+ + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/planetary2.html b/html/_modules/boxes/generators/planetary2.html new file mode 100644 index 0000000..f4fcd19 --- /dev/null +++ b/html/_modules/boxes/generators/planetary2.html @@ -0,0 +1,300 @@ + + + + + + + + boxes.generators.planetary2 — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.planetary2

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+import math
+
+
[docs]class Planetary2(Boxes): + + """Balanced force Difference Planetary Gear (not yet working properly)""" + + ui_group = "Unstable" + + description = """Still has issues. The middle planetary gears set must not have a mashing sun gear as it can't be a proper gear set.""" + + def __init__(self): + Boxes.__init__(self) + self.buildArgParser("nema_mount") + self.argparser.add_argument( + "--profile", action="store", type=str, default="GT2_2mm", + choices=pulley.Pulley.getProfiles(), + help="profile of the teeth/belt") + self.argparser.add_argument( + "--sunteeth", action="store", type=int, default=20, + help="number of teeth on sun gear") + self.argparser.add_argument( + "--planetteeth", action="store", type=int, default=20, + help="number of teeth on planets") + self.argparser.add_argument( + "--maxplanets", action="store", type=int, default=0, + help="limit the number of planets (0 for as much as fit)") + self.argparser.add_argument( + "--deltateeth", action="store", type=int, default=1, + help="enable secondary ring with given delta to the ring gear") + self.argparser.add_argument( + "--modulus", action="store", type=float, default=1.0, + help="modulus of the theeth in mm") + self.argparser.add_argument( + "--shaft", action="store", type=float, default=6., + help="diameter of the shaft") + self.argparser.add_argument( + "--screw1", action="store", type=float, default=2.4, + help="diameter of lower part of the screw hole") + self.argparser.add_argument( + "--screw2", action="store", type=float, default=4., + help="diameter of upper part of the screw hole") + self.argparser.add_argument( + "--pinsize", action="store", type=float, default=3.1, + help="diameter of alignment pins") + # self.argparser.add_argument( + # "--stages", action="store", type=int, default=4, + # help="number of stages in the gear reduction") + + def pins(self, r, rh, nr=0, angle=0.0): + self.moveTo(0, 0, angle) + + if nr < 8: + ang = 20 + 10 * nr + else: + ang = 15 + 10 * (nr-8) + + ang = 180 - ang + for a in (0, ang, -ang): + self.moveTo(0, 0, a) + self.hole(r, 0, rh) + self.moveTo(0, 0, -a) + + + def render(self): + + ringteeth = self.sunteeth + 2 * self.planetteeth + t = self.thickness + spoke_width = 4 * t + pinsize = self.pinsize / 2. + + pitch1, size1, xxx = self.gears.sizes(teeth=self.sunteeth, + dimension=self.modulus) + pitch2, size2, xxx = self.gears.sizes(teeth=self.planetteeth, + dimension=self.modulus) + pitch3, size3, xxx = self.gears.sizes( + teeth=ringteeth, internal_ring=True, spoke_width=spoke_width, + dimension=self.modulus) + + planets = int(math.pi / (math.asin(float(self.planetteeth + 2) / (self.planetteeth + self.sunteeth)))) + + if self.maxplanets: + planets = min(self.maxplanets, planets) + + # Make sure the teeth mash + ta = self.sunteeth + ringteeth + # There are sunteeth+ringteeth mashing positions for the planets + planetpositions = [round(i * ta / planets) * 360 / ta for i in range(planets)] + secondary_offsets = [((pos % (360. / (ringteeth - self.deltateeth))) - + (pos % (360. / ringteeth)) * ringteeth / self.planetteeth) + for pos in planetpositions] + + ratio = (1 + (ringteeth / self.sunteeth)) * (-ringteeth/self.deltateeth) + # XXX make configurable? + profile_shift = 20 + pressure_angle = 20 + + screw = self.screw1 / 2 + + # output + # XXX simple guess + belt = self.profile + pulleyteeth = int((size3-2*t) * math.pi / pulley.Pulley.spacing[belt][1]) + numplanets = planets + + deltamodulus = self.modulus * ringteeth / (ringteeth - self.deltateeth) + + def holes(r): + def h(): + self.hole(2*t, 2*t, r) + self.hole(size3-2*t, 2*t, r) + self.hole(2*t, size3-2*t, r) + self.hole(size3-2*t, size3-2*t, r) + return h + + def planets(): + self.moveTo(size3/2, size3/2) + for angle in planetpositions: + angle += 180 # compensate for 3 postion in callback + self.moveTo(0, 0, angle) + self.hole((pitch1+pitch2), 0, size2/2) + self.moveTo(0, 0, -angle) + + # Base + self.rectangularWall(size3, size3, callback=[ + lambda: self.NEMA(self.nema_mount, size3 / 2, size3 / 2), + holes(screw), planets], + move="up") + + def gear(): + self.moveTo(size3 / 2, size3 / 2) + self.gears(teeth=ringteeth, dimension=self.modulus, + angle=pressure_angle, internal_ring=True, + spoke_width=spoke_width, teeth_only=True, + profile_shift=profile_shift, move="up") + + # Lower primary ring gear + self.rectangularWall(size3, size3, callback=[gear, holes(screw)], move="up") + tl = 0.5*size3*(2**0.5-1)*2**0.5 + screw = self.screw2 / 2 + self.rectangularTriangle(tl, tl, num=8, callback=[ + None, lambda:self.hole(2*t, 2*t, screw)], move='up') + + # Secondary ring gears + def ring(): + self.gears(teeth=ringteeth - self.deltateeth, + dimension=deltamodulus, + angle=pressure_angle, internal_ring=True, + spoke_width=spoke_width, teeth_only=True, + profile_shift=profile_shift) + for i in range(3): + self.hole((size3-6*t)/2+0.5*pinsize, 0, pinsize) + self.moveTo(0, 0, 120) + + self.pulley(pulleyteeth, belt, callback=ring, move="up") + self.pulley(pulleyteeth, belt, callback=ring, move="up") + + # Upper primary ring gear + self.rectangularWall(size3, size3, callback=[gear, holes(screw)], move="up") + # top cover plate + self.rectangularWall(size3, size3, callback=[holes(screw)], move="up") + + # Sun gear + def sunpins(): + self.hole(0.5*self.shaft+1.5*pinsize ,0, pinsize) + self.hole(-0.5*self.shaft-1.5*pinsize ,0, pinsize) + self.partsMatrix(4, 4, 'up', self.gears, teeth=self.sunteeth, + dimension=self.modulus, callback=sunpins, + angle=pressure_angle, mount_hole=self.shaft, + profile_shift=profile_shift) + + # Planets + for i in range(numplanets): + with self.saved_context(): + self.gears(teeth=self.planetteeth, dimension=self.modulus, + angle=pressure_angle, + callback=lambda:self.pins(0.25*size2, pinsize, i), + profile_shift=profile_shift, move="right") + for j in range(2): + self.gears(teeth=self.planetteeth, dimension=self.modulus, + angle=pressure_angle, + callback=lambda:self.pins(0.25*size2, pinsize, i, + secondary_offsets[i]), + profile_shift=profile_shift, move="right") + self.gears(teeth=self.planetteeth, dimension=self.modulus, + angle=pressure_angle, + callback=lambda:self.pins(0.25*size2, pinsize, i), + profile_shift=profile_shift, move="right") + + self.gears(teeth=self.planetteeth, dimension=self.modulus, + angle=pressure_angle, + profile_shift=profile_shift, move="up only") + + self.text("1:%.1f" % abs(ratio))
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/platonic.html b/html/_modules/boxes/generators/platonic.html new file mode 100644 index 0000000..d0e2671 --- /dev/null +++ b/html/_modules/boxes/generators/platonic.html @@ -0,0 +1,218 @@ + + + + + + + + boxes.generators.platonic — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.platonic

+#!/usr/bin/env python3
+# Copyright (C) 2020 Norbert Szulc
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes.edges import FingerJointBase, FingerJointEdge
+
+from math import sin, pi
+
+class UnevenFingerJointEdge(FingerJointEdge):
+    """Uneven finger joint edge """
+    char = 'u'
+    description = "Uneven Finger Joint"
+    positive = True
+
+    def __call__(self, length, bedBolts=None, bedBoltSettings=None, **kw):
+        # copied from original
+
+        positive = self.positive
+
+        s, f, thickness = self.settings.space, self.settings.finger, self.settings.thickness
+
+        p = 1 if positive else -1
+
+        fingers, leftover = self.calcFingers(length, bedBolts)
+
+        if not positive:
+            play = self.settings.play
+            f += play
+            s -= play
+            leftover -= play
+
+        shift = (f + s) / 2 # we shift all fingers to make them un even
+        if (leftover < shift): 
+            leftover = shift
+
+        self.edge((leftover + shift)/2, tabs=1)  # Whole point of this class
+
+        l1,l2 = self.fingerLength(self.settings.angle)
+        h = l1-l2
+
+        d = (bedBoltSettings or self.bedBoltSettings)[0]
+
+        for i in range(fingers):
+            if i != 0:
+                if not positive and bedBolts and bedBolts.drawBolt(i):
+                    self.hole(0.5 * s,
+                              0.5 * self.settings.thickness, 0.5 * d)
+
+                if positive and bedBolts and bedBolts.drawBolt(i):
+                    self.bedBoltHole(s, bedBoltSettings)
+                else:
+                    self.edge(s)
+
+            if positive and self.settings.style == "springs":
+                self.polyline(
+                    0, -90 * p, 0.8*h, (90 * p, 0.2*h),
+                    0.1 * h, 90, 0.9*h, -180, 0.9*h, 90,
+                    f - 0.6*h,
+                    90, 0.9*h, -180, 0.9*h, 90, 0.1*h,
+                (90 * p, 0.2 *h), 0.8*h, -90 * p)
+            else:
+                self.polyline(0, -90 * p, h, 90 * p, f, 90 * p, h, -90 * p)
+
+        self.edge((leftover - shift)/2, tabs=1)  # Whole point of this class
+
+# Unstable
+class UnevenFingerJointEdgeCounterPart(UnevenFingerJointEdge): 
+    """Uneven finger joint edge - other side"""
+    char = 'U'
+    description = "Uneven Finger Joint (opposing side)"
+    positive = False
+
+
[docs]class Platonic(Boxes): + """Platonic solids generator""" + + ui_group = "Unstable" # see ./__init__.py for names + description = """![Icosahedron](static/samples/Platonic-Icosahedron.jpg) +""" + + SOLIDS = { + "tetrahedron": (4, 3), + "cube": (6, 4), + "octahedron": (8, 3), + "dodecahedron": (12, 5), + "icosahedro": (20, 3), + } + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings, surroundingspaces=0) + + self.buildArgParser(x=60, outside=True) # x should be treated as edge length, TODO: change that + self.argparser.add_argument( + "--type", action="store", type=str, default=list(self.SOLIDS)[0], + choices=list(self.SOLIDS), + help="type of platonic solid") + + + def render(self): + # adjust to the variables you want in the local scope + e = self.x + t = self.thickness + faces, corners = self.SOLIDS[self.type] + + u = UnevenFingerJointEdge(self, self.edges["f"].settings) + self.addPart(u) + + uc = UnevenFingerJointEdgeCounterPart(self, self.edges["f"].settings) + self.addPart(uc) + + for _ in range(faces): + self.regularPolygonWall(corners, side=e, edges="u", move="right")
+ + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/polehook.html b/html/_modules/boxes/generators/polehook.html new file mode 100644 index 0000000..72d69f6 --- /dev/null +++ b/html/_modules/boxes/generators/polehook.html @@ -0,0 +1,219 @@ + + + + + + + + boxes.generators.polehook — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.polehook

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class PoleHook(Boxes): # change class name here and below + """Hook for pole like things to be clamped to another pole""" + + def __init__(self): + Boxes.__init__(self) + + # Uncomment the settings for the edge types you use + self.addSettingsArgs(edges.FingerJointSettings) + + # Add non default cli params if needed (see argparse std lib) + self.argparser.add_argument( + "--diameter", action="store", type=float, default=50., + help="diameter of the thing to hook") + self.argparser.add_argument( + "--screw", action="store", type=float, default=7.8, + help="diameter of the screw in mm") + self.argparser.add_argument( + "--screwhead", action="store", type=float, default=13., + help="with of the screw head in mm") + self.argparser.add_argument( + "--screwheadheight", action="store", type=float, default=5.5, + help="height of the screw head in mm") + self.argparser.add_argument( + "--pin", action="store", type=float, default=4., + help="diameter of the pin in mm") + + def fork(self, d, w, edge="e", full=True, move=None): + tw = d + 2 * w + th = 2 * d + + if self.move(tw, th, move, True): + return + + e = self.edges.get(edge, edge) + + + self.moveTo(0, e.margin()) + + if e is self.edges["e"]: + self.bedBoltHole(tw) + else: + e(tw, bedBolts=edges.Bolts(1)) + if full: + self.hole(-0.5*w, 2*d, self.pin/2) + self.polyline(0, 90, 2*d, (180, w/2), d, (-180, d/2), + 0.5*d, (180, w/2), 1.5 * d, 90) + else: + self.polyline(0, 90, d, 90, w, 90, 0, (-180, d/2), + 0.5*d, (180, w/2), 1.5 * d, 90) + + self.move(tw, th, move) + + def lock(self, l1, l2, w, move=None): + l1 += w/2 + l2 += w/2 + if self.move(l1, l2, move, True): + return + self.hole(w/2, w/2, self.pin/2) + self.moveTo(w/2, 0) + self.polyline(l2-w, (180, w/2), l2-2*w, (-90, w/2), l1-2*w, (180, w/2), + l1-w, (90, w/2)) + self.move(l1, l2, move) + + def backplate(self): + tw = self.diameter + 2*self.ww + t = self.thickness + b = edges.Bolts(1) + bs = (0.0, ) + self.fingerHolesAt(-tw/2, -2*t, tw, 0, bedBolts=b, bedBoltSettings=bs) + self.fingerHolesAt(-tw/2, 0, tw, 0, bedBolts=b, bedBoltSettings=bs) + self.fingerHolesAt(-tw/2, +2*t, tw, 0, bedBolts=b, bedBoltSettings=bs) + + def clamp(self): + d = self.diameter + 2 * self.ww + self.moveTo(10, -0.5*d, 90) + self.edge(d) + self.moveTo(0, -8, -180) + self.edge(d) + + def render(self): + # adjust to the variables you want in the local scope + d = self.diameter + t = self.thickness + + shh = self.screwheadheight + self.bedBoltSettings = (self.screw, self.screwhead, shh, d/4+shh, d/4) # d, d_nut, h_nut, l, l + self.ww = ww = 4*t + self.fork(d, ww, "f", move="right") + self.fork(d, ww, "f", move="right") + self.fork(d, ww, "f", full=False, move="right") + self.fork(d, ww, full=False, move="right") + self.fork(d, ww, full=False, move="right") + + self.parts.disc(d+2*ww, callback=self.backplate, hole=self.screw, move="right") + self.parts.disc(d+2*ww, hole=self.screw, move="right") + self.parts.disc(d+2*ww, callback=self.clamp, hole=self.screw+0.5*t, move="right") + self.parts.disc(d+2*ww, hole=self.screw+0.5*t, move="right") + self.parts.waivyKnob(50, callback=lambda:self.nutHole(self.screwhead), + move="right") + self.parts.waivyKnob(50, callback=lambda:self.nutHole(self.screwhead), + move="right") + self.parts.waivyKnob(50, hole=self.screw+0.5*t, move="right") + + ll = ((d**2 + (0.5*(d+ww))**2)**0.5) - 0.5 * d + for i in range(3): + self.lock(ll, ll, ww, move="right") + + for i in range(2): + self.parts.disc(ww, move="up")
+ + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/pulley.html b/html/_modules/boxes/generators/pulley.html new file mode 100644 index 0000000..41130c8 --- /dev/null +++ b/html/_modules/boxes/generators/pulley.html @@ -0,0 +1,172 @@ + + + + + + + + boxes.generators.pulley — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.pulley

+#!/usr/bin/python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes import pulley
+import math
+
+
+
[docs]class Pulley(Boxes): + """Timing belt pulleys for different profiles""" + + ui_group = "Part" + + def __init__(self): + Boxes.__init__(self) + # remove cli params you do not need + self.buildArgParser(h=6.) + self.argparser.add_argument( + "--profile", action="store", type=str, default="GT2_2mm", + choices=pulley.Pulley.getProfiles(), + help="profile of the teeth/belt") + self.argparser.add_argument( + "--teeth", action="store", type=int, default=20, + help="number of teeth") + self.argparser.add_argument( + "--axle", action="store", type=float, default=5, + help="diameter of the axle") + self.argparser.add_argument( + "--insideout", action="store", type=BoolArg(), default=False, + help="create a ring gear with the belt being pushed against from within") + self.argparser.add_argument( + "--top", action="store", type=float, default=0, + help="overlap of top rim (zero for none)") + + # Add non default cli params if needed (see argparse std lib) + # self.argparser.add_argument( + # "--XX", action="store", type=float, default=0.5, + # help="DESCRIPTION") + + def disk(self, diameter, hole, callback=None, move=""): + w = diameter + 2 * self.spacing + + if self.move(w, w, move, before=True): + return + + self.moveTo(w / 2, w / 2) + self.cc(callback, None, 0.0, 0.0) + + if hole: + self.hole(0, 0, hole / 2.0) + + self.moveTo(diameter / 2 + self.burn, 0, 90) + self.corner(360, diameter / 2) + self.move(w, w, move) + + def render(self): + # adjust to the variables you want in the local scope + t = self.thickness + + if self.top: + self.disk( + self.pulley.diameter(self.teeth, self.profile) + 2 * self.top, + self.axle, move="right") + + for i in range(int(math.ceil(self.h / self.thickness))): + self.pulley(self.teeth, self.profile, insideout=self.insideout, r_axle=self.axle / 2.0, move="right")
+ + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/rack10box.html b/html/_modules/boxes/generators/rack10box.html new file mode 100644 index 0000000..608934d --- /dev/null +++ b/html/_modules/boxes/generators/rack10box.html @@ -0,0 +1,114 @@ + + + + + + + + boxes.generators.rack10box — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.rack10box

+#!/usr/bin/env python3
+# Copyright (C) 2018 Sebastian Reichel
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes.generators.rack19box import Rack19Box
+
+
[docs]class Rack10Box(Rack19Box): + """Closed box with screw on top for mounting in a 10" rack.""" + + def render(self): + self._render(type=10)
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/rack19box.html b/html/_modules/boxes/generators/rack19box.html new file mode 100644 index 0000000..a30d61a --- /dev/null +++ b/html/_modules/boxes/generators/rack19box.html @@ -0,0 +1,187 @@ + + + + + + + + boxes.generators.rack19box — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.rack19box

+#!/usr/bin/env python3
+# Copyright (C) 2013-2018 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
+
[docs]class Rack19Box(Boxes): + """Closed box with screw on top for mounting in a 19" rack.""" + + ui_group = "Box" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings, surroundingspaces=0.5) + self.argparser.add_argument( + "--depth", action="store", type=float, default=100., + help="inner depth in mm") + self.argparser.add_argument( + "--height", action="store", type=int, default=2, + choices=list(range(1, 17)), + help="height in rack units") + self.argparser.add_argument( + "--triangle", action="store", type=float, default=25., + help="Sides of the triangles holding the lid in mm") + self.argparser.add_argument( + "--d1", action="store", type=float, default=2., + help="Diameter of the inner lid screw holes in mm") + self.argparser.add_argument( + "--d2", action="store", type=float, default=3., + help="Diameter of the lid screw holes in mm") + + def wallxCB(self): + t = self.thickness + self.fingerHolesAt(0, self.h-1.5*t, self.triangle, 0) + self.fingerHolesAt(self.x, self.h-1.5*t, self.triangle, 180) + + def wallxfCB(self): # front + t = self.thickness + for x in (8.5, self.x+2*17.+2*t-8.5): + for y in (6., self.h-6.+t): + self.rectangularHole(x, y, 10, 6.5, r=3.25) + + self.moveTo(t+17., t) + self.wallxCB() + + def wallyCB(self): + t = self.thickness + self.fingerHolesAt(0, self.h-1.5*t, self.triangle, 0) + self.fingerHolesAt(self.y, self.h-1.5*t, self.triangle, 180) + + + def _render(self, type): + + t = self.thickness + self.h = h = self.height * 44.45 - 0.787 - t + if type == 10: + self.x = 219.0 - 2*t + else: + self.x = 448.0 - 2*t + x = self.x + y = self.y = self.depth + + d1, d2 =self.d1, self.d2 + tr = self.triangle + trh = tr / 3. + + self.rectangularWall(y, h, "ffef", callback=[self.wallyCB], move="right") + self.rectangularWall(x, h, "fFeF", callback=[self.wallxCB], + move="up") + self.flangedWall(x, h, "FFeF", callback=[self.wallxfCB], r=t, + flanges=[0., 17., -t, 17.]) + self.rectangularWall(y, h, "ffef", callback=[self.wallyCB], + move="left up") + + self.rectangularWall(x, y, "fFFF", move="right") + self.rectangularWall(x, y, callback=[ + lambda:self.hole(trh, trh, d=d2)] * 4, move='up') + + self.rectangularTriangle(tr, tr, "ffe", num=4, + callback=[None, lambda: self.hole(trh, trh, d=d1)]) + + + def render(self): + self._render(type=19)
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/rack19halfwidth.html b/html/_modules/boxes/generators/rack19halfwidth.html new file mode 100644 index 0000000..1f5bd30 --- /dev/null +++ b/html/_modules/boxes/generators/rack19halfwidth.html @@ -0,0 +1,186 @@ + + + + + + + + boxes.generators.rack19halfwidth — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.rack19halfwidth

+"""Half 19inch rack unit for musical equipment."""
+
+from boxes import Boxes
+from boxes.edges import Edge
+
+
[docs]class Rack19HalfWidth(Boxes): + """Half width 19inch rack unit for musical equipment.""" + + ui_group = "Box" + + def __init__(self): + super().__init__() + self.argparser.add_argument( + '--ru_count', action='store', type=float, default=1, + help='number of rack units') + self.argparser.add_argument( + '--holes', action='store', type=str, default="xxmpwx", + help='mounting patterns: x=xlr, m=midi, p=9v-power, w=6.5mm-wire, space=next row') + self.argparser.add_argument( + '--z', action='store', type=float, default=20, + help='depth of the shorter (rackear) side') + self.argparser.add_argument( + '--deepz', action="store", type=float, default=124, + help='depth of the longer (screwed to another half sized thing) side') + + def render(self): + """Render box.""" + # pylint: disable=invalid-name + t = self.thickness + z = self.z + self.x = x = 223 - (2 * t) + self.y = y = (self.ru_count * 44.45) - 4.45 - (2 * t) + deepz = self.deepz + + # front + self.flangedWall(x, y, "FFFF", callback=[self.util_holes, self.rack_holes], r=t, + flanges=[0, 17, 0, 0], move="up") + + # top&bottom + self.trapezoidWall(x, deepz, z, "fFeF", move="up") + self.trapezoidWall(x, deepz, z, "fFeF", move="up") + + # side + self.rectangularWall(deepz, y, "fffe", move="right") + self.rectangularWall(z, y, "fffe", move="up") + + def rack_holes(self): + """Rackmount holes.""" + t = self.thickness # pylint: disable=invalid-name + self.rectangularHole(6 + t, 10, 10, 6.5, r=3.25) + self.rectangularHole(self.y - 6 + t, 10, 10, 6.5, r=3.25) + + def util_holes(self): + """Add holes.""" + self.moveTo(10, (44.45 - 4.45)/2) + for line in self.holes.split(): + with self.saved_context(): + for hole in line: + self.hole_map.get(hole, lambda _: None)(self) + self.moveTo(0, 44.45) + + def hole_xlr(self): + """Hole for a xlr port.""" + self.moveTo(16) + self.hole(-9.5, 12, 1) + self.hole(0, 0, 11.8) + self.hole(9.5, -12, 1) + self.moveTo(16) + + def hole_midi(self): + """Hole for a midi port.""" + self.moveTo(17) + self.hole(-11.1, 0, 1) + self.hole(0, 0, 7.5) + self.hole(11.1, 0, 1) + self.moveTo(17) + + def hole_power(self): + """Hole for a 9v power port.""" + self.moveTo(11) + self.rectangularHole(0, 0, 9, 11) + self.moveTo(11) + + def hole_wire(self): + """Hole for a wire.""" + self.moveTo(3) + self.hole(0, 0, 3.25) + print('hi') + self.moveTo(3) + + hole_map = { + 'm': hole_midi, + 'p': hole_power, + 'w': hole_wire, + 'x': hole_xlr, + }
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/rackbox.html b/html/_modules/boxes/generators/rackbox.html new file mode 100644 index 0000000..d645bc2 --- /dev/null +++ b/html/_modules/boxes/generators/rackbox.html @@ -0,0 +1,187 @@ + + + + + + + + boxes.generators.rackbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.rackbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2017 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
+
[docs]class RackBox(Boxes): + """Closed box with screw on top and mounting holes""" + + ui_group = "Box" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings, surroundingspaces=1.2) + self.buildArgParser("x", "y", "h", "outside") + self.argparser.add_argument( + "--triangle", action="store", type=float, default=25., + help="Sides of the triangles holding the lid in mm") + self.argparser.add_argument( + "--d1", action="store", type=float, default=2., + help="Diameter of the inner lid screw holes in mm") + self.argparser.add_argument( + "--d2", action="store", type=float, default=3., + help="Diameter of the lid screw holes in mm") + self.argparser.add_argument( + "--d3", action="store", type=float, default=4., + help="Diameter of the mounting screw holes in mm") + self.argparser.add_argument( + "--holedist", action="store", type=float, default=7., + help="Distance of the screw holes from the wall in mm") + + def wallxCB(self): + t = self.thickness + self.fingerHolesAt(0, self.h-1.5*t, self.triangle, 0) + self.fingerHolesAt(self.x, self.h-1.5*t, self.triangle, 180) + + def wallxfCB(self): # front + t = self.thickness + hd = self.holedist + for x in (hd, self.x+3*hd+2*t): + for y in (hd, self.h-hd+t): + self.hole(x, y, self.d3/2.) + + self.moveTo(t+2*hd, t) + self.wallxCB() + + def wallyCB(self): + t = self.thickness + self.fingerHolesAt(0, self.h-1.5*t, self.triangle, 0) + self.fingerHolesAt(self.y, self.h-1.5*t, self.triangle, 180) + + + def render(self): + + t = self.thickness + self.h = h = self.h + 2*t # compensate for lid + x, y, h = self.x, self.y, self.h + d1, d2, d3 =self.d1, self.d2, self.d3 + hd = self.holedist + tr = self.triangle + trh = tr / 3. + + if self.outside: + self.x = x = self.adjustSize(x) + self.y = y = self.adjustSize(y) + self.h = h = h - 3*t + + self.rectangularWall(x, h, "fFeF", callback=[self.wallxCB], + move="right") + self.rectangularWall(y, h, "ffef", callback=[self.wallyCB], move="up") + self.flangedWall(x, h, "FFeF", callback=[self.wallxfCB], r=t, + flanges=[0., 2*hd, -t, 2*hd]) + self.rectangularWall(y, h, "ffef", callback=[self.wallyCB], + move="left up") + + self.rectangularWall(x, y, "fFFF", move="right") + self.rectangularWall(x, y, callback=[ + lambda:self.hole(trh, trh, d=d2)] * 4, move='up') + + self.rectangularTriangle(tr, tr, "ffe", num=4, + callback=[None, lambda: self.hole(trh, trh, d=d1)])
+ + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/rectangularWall.html b/html/_modules/boxes/generators/rectangularWall.html new file mode 100644 index 0000000..a4e91eb --- /dev/null +++ b/html/_modules/boxes/generators/rectangularWall.html @@ -0,0 +1,161 @@ + + + + + + + + boxes.generators.rectangularWall — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.rectangularWall

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class RectangularWall(Boxes): + """Simple wall""" + + ui_group = "Part" # see ./__init__.py for names + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.CabinetHingeSettings) + self.addSettingsArgs(edges.ClickSettings) + self.addSettingsArgs(edges.DoveTailSettings) + self.addSettingsArgs(edges.FingerJointSettings) + self.addSettingsArgs(edges.GearSettings) + self.addSettingsArgs(edges.GripSettings) + self.addSettingsArgs(edges.HingeSettings) + self.addSettingsArgs(edges.ChestHingeSettings) + self.addSettingsArgs(edges.LidSettings) + self.addSettingsArgs(edges.StackableSettings) + + self.buildArgParser(x=100, h=100) + + self.argparser.add_argument( + "--bottom_edge", action="store", + type=ArgparseEdgeType("cCdDeEfFghiIjJkKlLmMnNoOpPqQRsSšŠuUvV"), choices=list("cCdDeEfFghiIjJkKlLmMnNoOpPqQRsSšŠuUvV"), + default="e", help="edge type for bottom edge") + self.argparser.add_argument( + "--right_edge", action="store", + type=ArgparseEdgeType("cCdDeEfFghiIjJkKlLmMnNoOpPqQRsSšŠuUvV"), choices=list("cCdDeEfFghiIjJkKlLmMnNoOpPqQRsSšŠuUvV"), + default="e", help="edge type for right edge") + self.argparser.add_argument( + "--top_edge", action="store", + type=ArgparseEdgeType("cCdDeEfFghiIjJkKlLmMnNoOpPqQRsSšŠuUvV"), choices=list("cCdDeEfFghiIjJkKlLmMnNoOpPqQRsSšŠuUvV"), + default="e", help="edge type for top edge") + self.argparser.add_argument( + "--left_edge", action="store", + type=ArgparseEdgeType("cCdDeEfFghiIjJkKlLmMnNoOpPqQRsSšŠuUvV"), choices=list("cCdDeEfFghiIjJkKlLmMnNoOpPqQRsSšŠuUvV"), + default="e", help="edge type for left edge") + + + def cb(self, nr): + t = self.thickness + if self.edgetypes[nr] == "f": + self.fingerHolesAt(0, -2.5*t, self.h if nr % 2 else self.x, 0) + + def render(self): + # adjust to the variables you want in the local scope + t = self.thickness + + self.edgetypes = [self.bottom_edge, self.right_edge, self.top_edge, self.left_edge] + + self.moveTo(3*t, 3*t) + self.rectangularWall(self.x, self.h, self.edgetypes, callback=self.cb)
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/regularbox.html b/html/_modules/boxes/generators/regularbox.html new file mode 100644 index 0000000..ef74b78 --- /dev/null +++ b/html/_modules/boxes/generators/regularbox.html @@ -0,0 +1,205 @@ + + + + + + + + boxes.generators.regularbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.regularbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes.generators.bayonetbox import BayonetBox
+import copy
+
+
[docs]class RegularBox(BayonetBox): + """Box with regular polygon as base""" + + description = """For short side walls that don't fit a connecting finger reduce *surroundingspaces* and *finger* in the Finger Joint Settings. + +The lids needs to be glued. For the bayonet lid all outside rings attach to the bottom, all inside rings to the top. +""" + + ui_group = "Box" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings, surroundingspaces=1) + self.buildArgParser("h", "outside") + self.argparser.add_argument( + "--radius", action="store", type=float, default=50.0, + help="inner radius if the box (at the corners)") + self.argparser.add_argument( + "--n", action="store", type=int, default=5, + help="number of sides") + self.argparser.add_argument( + "--top", action="store", type=str, default="none", + choices=["none", "hole", "angled hole", "angled lid", "angled lid2", "round lid", "bayonet mount"], + help="style of the top and lid") + self.argparser.add_argument( + "--alignment_pins", action="store", type=float, default=1.0, + help="diameter of the alignment pins for bayonet lid") + + self.lugs=6 + + def render(self): + + r, h, n = self.radius, self.h, self.n + + if self.outside: + r = r = r - self.thickness / math.cos(math.radians(360/(2*n))) + if self.top == "none": + h = self.adjustSize(h, False) + elif "lid" in self.top and self.top != "angled lid": + h = self.adjustSize(h) - self.thickness + else: + h = self.adjustSize(h) + + t = self.thickness + + fingerJointSettings = copy.deepcopy(self.edges["f"].settings) + fingerJointSettings.setValues(self.thickness, angle=360./n) + fingerJointSettings.edgeObjects(self, chars="gGH") + + r, sh, side = self.regularPolygon(n, radius=r) + + with self.saved_context(): + self.regularPolygonWall(corners=n, r=r, edges='F', move="right") + if self.top == "angled lid": + self.regularPolygonWall(corners=n, r=r, edges='e', move="right") + self.regularPolygonWall(corners=n, r=r, edges='E', move="right") + elif self.top in ("angled hole", "angled lid2"): + self.regularPolygonWall(corners=n, r=r, edges='F', move="right", + callback=[lambda:self.regularPolygonAt( + 0, 0, n, h=sh-t)]) + if self.top == "angled lid2": + self.regularPolygonWall(corners=n, r=r, edges='E', move="right") + elif self.top in ("hole", "round lid"): + self.regularPolygonWall(corners=n, r=r, edges='F', move="right", + hole=(sh-t)*2) + if self.top == "round lid": + self.parts.disc(sh*2, move="right") + if self.top == "bayonet mount": + self.diameter = 2*sh + self.parts.disc(sh*2-0.1*t, callback=self.lowerCB, + move="right") + self.regularPolygonWall(corners=n, r=r, edges='F', + callback=[self.upperCB], move="right") + self.parts.disc(sh*2, move="right") + + self.regularPolygonWall(corners=n, r=r, edges='F', move="up only") + + side = 2 * math.sin(math.radians(180.0/n)) * r + fingers = self.top in ("hole", "angled hole", "round lid", + "angled lid2", "bayonet mount") + + if n % 2: + for i in range(n): + self.rectangularWall(side, h, move="right", + edges="fgfG" if fingers else "fgeG") + else: + for i in range(n//2): + self.rectangularWall(side, h, move="right", + edges="fGfG" if fingers else "fGeG") + self.rectangularWall(side, h, move="right", + edges="fgfg" if fingers else "fgeg")
+ + + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/regularstarbox.html b/html/_modules/boxes/generators/regularstarbox.html new file mode 100644 index 0000000..5a07233 --- /dev/null +++ b/html/_modules/boxes/generators/regularstarbox.html @@ -0,0 +1,194 @@ + + + + + + + + boxes.generators.regularstarbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.regularstarbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+import copy
+
+class SlotEdge(edges.Edge):
+
+    def __call__(self, length, **kw):
+        t, n = self.settings.thickness, self.settings.n
+        r, h = self.settings.radius, self.settings.h
+        sh = self.settings.sh # distance side to center
+
+        li = 2 * sh * math.tan(math.radians(90/n)) # side inner 2x polygone
+        ls2 = t / math.tan(math.radians(180/n))
+        ls1 = t / math.cos(math.radians(90-(180/n)))
+
+        lo = (length-li-2*ls1)/2
+
+        li = li - 2*ls2 # correct for overlap of wall
+        
+        d = h/2
+        
+        if li > 0:
+            poly = [lo-1, (90, 1), d+t-1, -90, ls1+ls2, -90, d-t, (90, t)]
+        self.polyline(*(poly + [li-2*t] + list(reversed(poly))))
+
+    def startwidth(self):
+        return self.settings.thickness
+
+
+
[docs]class RegularStarBox(Boxes): + """Regular polygon boxes that form a star when closed""" + + ui_group = "Box" + + + description = """![Open box](static/samples/RegularStarBox-2.jpg)""" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.buildArgParser("h", "outside") + self.argparser.add_argument( + "--radius", action="store", type=float, default=50.0, + help="inner radius if the box (center to corners)") + self.argparser.add_argument( + "--n", action="store", type=int, default=5, + choices=(3, 4, 5), + help="number of sides") + + def render(self): + + r, h, n = self.radius, self.h, self.n + + if self.outside: + self.r = r = r - self.thickness / math.cos(math.radians(360/(2*n))) + self.h = h = self.adjustSize(h) + + t = self.thickness + + fingerJointSettings = copy.deepcopy(self.edges["f"].settings) + fingerJointSettings.setValues(self.thickness, angle=360./n) + fingerJointSettings.edgeObjects(self, chars="gGH") + + self.edges["e"] = SlotEdge(self, self) + + r, sh, side = self.regularPolygon(n, radius=r) + self.sh = sh + + with self.saved_context(): + self.regularPolygonWall(corners=n, r=r, edges='F', move="right") + self.regularPolygonWall(corners=n, r=r, edges='F', move="right") + + self.regularPolygonWall(corners=n, r=r, edges='F', move="up only") + + for s in range(2): + with self.saved_context(): + if n % 2: + for i in range(n): + self.rectangularWall(side, h, move="right", + edges="fgeG") + else: + for i in range(n//2): + self.rectangularWall(side, h, move="right", + edges="fGeG") + self.rectangularWall(side, h, move="right", + edges="fgeg") + + self.rectangularWall(side, h, move="up only", + edges="fgeG")
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/robotarm.html b/html/_modules/boxes/generators/robotarm.html new file mode 100644 index 0000000..3d0ca01 --- /dev/null +++ b/html/_modules/boxes/generators/robotarm.html @@ -0,0 +1,149 @@ + + + + + + + + boxes.generators.robotarm — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.robotarm

+#!/usr/bin/env python3
+# Copyright (C) 2017 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes import robot, servos
+
+
[docs]class RobotArm(Boxes): # change class name here and below + """Segments of servo powered robot arm""" + + ui_group = "Part" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings) + for i in range(1, 6): + ra = robot.RobotArg(True) + sa = servos.ServoArg() + self.argparser.add_argument( + "--type%i" % i, action="store", type=ra, + default="none", choices=ra.choices(), + help="type of arm segment") + self.argparser.add_argument( + "--servo%ia" % i, action="store", type=sa, default="Servo9g", + choices=sa.choices(), help="type of servo to use") + self.argparser.add_argument( + "--servo%ib" % i, action="store", type=sa, default="Servo9g", + choices=sa.choices(), help="type of servo to use on second side (if different is supported)") + self.argparser.add_argument( + "--length%i" % i, action="store", type=float, default=50., + help="length of segment axle to axle") + + def render(self): + + for i in range(5, 0,-1): + armtype = getattr(self, "type%i" % i) + length = getattr(self, "length%i" % i) + servoA = getattr(self, "servo%ia" % i) + servoB = getattr(self, "servo%ib" % i) + armcls = getattr(robot, armtype, None) + if not armcls: + continue + servoClsA = getattr(servos, servoA) + servoClsB = getattr(servos, servoB) + armcls(self, servoClsA(self), servoClsB(self))(length, move="up")
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/rotary.html b/html/_modules/boxes/generators/rotary.html new file mode 100644 index 0000000..d1fa6a7 --- /dev/null +++ b/html/_modules/boxes/generators/rotary.html @@ -0,0 +1,419 @@ + + + + + + + + boxes.generators.rotary — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.rotary

+#!/usr/bin/python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
+class MotorEdge(edges.BaseEdge):
+    # def margin(self):
+    #    return 30
+    def __call__(self, l, **kw):
+        self.polyline(
+            l - 165, 45,
+            25 * 2 ** 0.5, -45,
+            60, -45,
+            25 * 2 ** 0.5, 45,
+            55)
+
+
+class OutsetEdge(edges.OutSetEdge):
+    def startwidth(self):
+        return 20
+
+
+class HangerEdge(edges.BaseEdge):
+    char = "H"
+
+    def margin(self):
+        return 40
+
+    def __call__(self, l, **kw):
+        self.fingerHolesAt(0, -0.5 * self.thickness, l, angle=0)
+        w = self.settings
+        self.polyline(0, -90,
+                      22 + w, 90,
+                      70, 135,
+                      2 ** 0.5 * 12, 45,
+                      35, -45,
+                      2 ** 0.5 * 0.5 * w, -90,
+                      2 ** 0.5 * 0.5 * w, -45,
+                      l - 28, 45,
+                      2 ** 0.5 * 5, 45, 5, -90)
+
+
+class RollerEdge(edges.BaseEdge):
+    def margin(self):
+        return 20
+
+    def __call__(self, l, **kw):
+        m = 40 + 100
+        self.polyline((l - m) / 2.0, -45,
+                      2 ** 0.5 * 20, 45,
+                      100, 45,
+                      2 ** 0.5 * 20, -45,
+                      (l - m) / 2.0)
+
+
+class RollerEdge2(edges.BaseEdge):
+    def margin(self):
+        return self.thickness
+
+    def __call__(self, l, **kw):
+        a = 30
+        f = 1 / math.cos(math.radians(a))
+        self.edges["f"](70)
+        self.polyline(0, a, f * 25, -a, l - 190, -a, f * 25, a, 0)
+        self.edges["f"](70)
+
+
+
[docs]class Rotary(Boxes): + + """Rotary Attachment for engraving cylindrical objects in a laser cutter""" + + ui_group = "Unstable" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings) + + self.argparser.add_argument( + "--diameter", action="store", type=float, default=72., + help="outer diameter of the wheels (including O rings)") + self.argparser.add_argument( + "--rubberthickness", action="store", type=float, default=5., + help="diameter of the strings of the O rings") + self.argparser.add_argument( + "--axle", action="store", type=float, default=6., + help="diameter of the axles") + self.argparser.add_argument( + "--knifethickness", action="store", type=float, default=8., + help="thickness of the knifes in mm. Use 0 for use with honey comb table.") + self.argparser.add_argument( + "--beamwidth", action="store", type=float, default=32., + help="width of the (aluminium) profile connecting the parts") + self.argparser.add_argument( + "--beamheight", action="store", type=float, default=7.1, + help="height of the (aluminium) profile connecting the parts") + + def mainPlate(self): + # Motor block outer side + t = self.thickness + d = self.diameter + a = self.axle + bw, bh = self.beamwidth, self.beamheight + hh = 0.5 * d + bh + 2 # hole height + self.hole(1.0 * d, hh, a/2.) + #self.hole(1.0 * d, hh, d/2.) + self.hole(2.0 * d + 5, hh, a/2.) + #self.hole(2.0 * d + 5, hh, d/2.) + # Main beam + self.rectangularHole(1.5*d+2.5, 0.5*bh, bw, bh) + + def frontPlate(self): + # Motor block inner side with motor mount + t = self.thickness + d = self.diameter + a = self.axle + bw, bh = self.beamwidth, self.beamheight + hh = 0.5 * d + bh + 2 # hole height + self.hole(1.0 * d, hh, a/2.) + #self.hole(1.0 * d, hh, d/2.) + self.hole(2.0 * d + 5, hh, a/2.) + #self.hole(2.0 * d + 5, hh, d/2.) + # Main beam + self.rectangularHole(1.5 * d+2.5, 0.5 * bh, bw, bh) + # Motor + mx = 2.7 * d + 20 + self.rectangularHole(mx, hh, 36 + 20, 36, r=36 / 2.0) + + for x in (-1, 1): + for y in (-1,1): + self.rectangularHole(mx+x * 25, hh + y * 25, 20, 4, r=2) + + def link(self, x, y, a, middleHole=False, move=None): + t = self.thickness + overallwidth = x + y + overallheight = y + ra = a / 2.0 + + if self.move(overallwidth, overallheight, move, before=True): + return + + self.moveTo(y / 2.0, 0) + self.hole(0, y / 2., ra) + self.hole(x, y / 2., ra) + + if middleHole: + self.hole(x / 2., y / 2., ra) + + self.edge(10) + self.edges["F"](60) + self.polyline(x - 70, (180, y / 2.), x, (180, y / 2.)) + + self.move(overallwidth, overallheight, move) + + def holderBaseCB(self): + bw, bh = self.beamwidth, self.beamheight + self.hole(20, self.hh - 10, self.a / 2) + self.rectangularHole(self.hl - 70, self.hh - 10, 110, self.a, r=self.a / 2) + self.rectangularHole(self.hl / 2, 0.5 * bh, bw, bh) + + def holderTopCB(self): + self.fingerHolesAt(0, 30 - 0.5 * self.thickness, self.hl, 0) + d = self.diameter / 2.0 + 1 + # XXX + y = -0.5 * self.diameter + self.th + self.hh - self.beamheight - 2. + self.hole(self.hl / 2 + d, y, self.axle / 2.0) + self.hole(self.hl / 2 - d, y, self.axle / 2.0) + self.hole(self.hl / 2 + d, y, self.diameter / 2.0) + self.hole(self.hl / 2 - d, y, self.diameter / 2.0) + + def render(self): + # adjust to the variables you want in the local scope + t = self.thickness + d = self.diameter + a = self.a = self.axle + bw, bh = self.beamwidth, self.beamheight + + # self.spacing = 0.1 * t + + # Change settings of default edges if needed. E.g.: + self.edges["f"].settings.setValues(self.thickness, space=2, finger=2, + surroundingspaces=1) + if self.knifethickness: + self.addPart(HangerEdge(self, self.knifethickness)) + else: + self.edges["H"] = self.edges["F"] + + # Holder + hw = self.hw = 70. + hh = self.hh = 35. + bh + hl = self.hl = 240 + # Base + self.rectangularWall(hl, hh, edges="hfef", callback=[self.holderBaseCB, None, + lambda: self.rectangularHole(hl / 2 + 50, hh - t / 2 - 1, + 60, t + 2)], move="up") + self.rectangularWall(hl, hh, edges="hfef", callback=[self.holderBaseCB], move="up") + self.rectangularWall(hl, hw, edges="ffff", callback=[lambda: self.hole(hl / 2 - 16 - 20, 25, 5)], move="up") + + with self.saved_context(): + self.rectangularWall(hw, hh, edges="hFeF", callback=[ + lambda: self.hole(hw / 2, hh - 20, 4)],move="right") + self.rectangularWall(hw, hh, edges="hFeF", move="right") + # Top + th = self.th = 30 + # sides + + self.rectangularWall(hw + 20, th, edges="fFeF", move="right", + callback=[lambda: self.fingerHolesAt(20 - 0.5 * t, 0, th)]) + self.rectangularWall(hw + 20, th, edges="fFeF", move="right", + callback=[lambda: self.fingerHolesAt(20 - 0.5 * t, 0, th)]) + + self.rectangularWall(hw, hh, edges="hFeF", move="up only") + outset = OutsetEdge(self, None) + roller2 = RollerEdge2(self, None) + self.rectangularWall(hl, th, edges=[roller2, "f", "e", "f"], callback=[ + lambda: self.hole(20, 15, a / 2), None, lambda: self.rectangularHole(50, th - 15, 70, a, r=a / 2)], + move="up") + self.rectangularWall(hl, th, edges=[roller2, "f", "e", "f"], callback=[ + lambda: self.hole(20, 15, a / 2), None, lambda: self.rectangularHole(50, th - 15 - t, 70, a, r=a / 2)], + move="up") + self.rectangularWall(hl, th, edges=[roller2, "f", RollerEdge(self, None), "f"], callback=[ + self.holderTopCB], move="up") + self.rectangularWall(hl, 20 - t, edges="feee", move="up") + tl = 70 + self.rectangularWall(tl, hw + 20, edges="FeFF", move="right", + callback=[None, lambda: self.fingerHolesAt(20 - 0.5 * t, 0, tl)]) + self.rectangularWall(tl, hw + 20, edges="FeFF", move="", + callback=[None, lambda: self.fingerHolesAt(20 - 0.5 * t, 0, tl)]) + self.rectangularWall(tl, hw + 20, edges="FeFF", move="left up only", + callback=[None, lambda: self.fingerHolesAt(20 - 0.5 * t, 0, tl)]) + + # Links + self.link(hl - 40, 25, a, True, move="up") + self.link(hl - 40, 25, a, True, move="up") + self.link(hl - 40, 25, a, True, move="up") + self.link(hl - 40, 25, a, True, move="up") + + with self.saved_context(): + self.rectangularWall(hw - 2 * t - 2, 60, edges="efef", move="right") + self.rectangularWall(hw - 4 * t - 4, 60, edges="efef", move="right") + # Spindel auxiliaries + self.parts.waivyKnob(50, callback=lambda: self.nutHole("M8"), move="right") + self.parts.waivyKnob(50, callback=lambda: self.nutHole("M8"), move="right") + + self.rectangularWall(hw - 2 * t - 4, 60, edges="efef", move="up only") + + with self.saved_context(): + slot = edges.SlottedEdge(self, [(30 - t) / 2, (30 - t) / 2], slots=15) + self.rectangularWall(30, 30, edges=["e", "e", slot, "e"], + callback=[lambda: self.hole(7, 23, self.axle / 2)], move="right") + self.rectangularWall(30, 30, edges=["e", "e", slot, "e"], + callback=[lambda: self.hole(7, 23, self.axle / 2)], move="right") + leftover = (hw - 6 * t - 6 - 20) / 2.0 + slot = edges.SlottedEdge(self, [leftover, 20, leftover], slots=15) + self.rectangularWall(hw - 4 * t - 6, 30, edges=[slot, "e", "e", "e"], + callback=[lambda: self.hole((hw - 4 * t - 6) / 2., 15, 4)], move="right") + for i in range(3): + self.rectangularWall(20, 30, + callback=[lambda: self.nutHole("M8", 10, 15)], move="right") + self.rectangularWall(20, 30, + callback=[lambda: self.hole(10, 15, 4)], move="right") + + self.rectangularWall(30, 30, move="up only") + + self.h = h = bh + 2 + 1.0 * d # height of outer pieces + # Other side + if self.knifethickness: + ow = 10 + self.rectangularWall(3.6 * d, h, edges="hfFf", callback=[ + lambda:self.rectangularHole(1.8 * d, 0.5 * bh, bw, bh)], + move="up") + self.rectangularWall(3.6 * d, h, edges="hfFf", callback=[ + lambda:self.rectangularHole(1.8 * d, 0.5 * bh, bw, bh)], + move="up") + self.rectangularWall(3.6 * d, ow, edges="ffff", move="up") + self.rectangularWall(3.6 * d, ow, edges="ffff", move="up") + with self.saved_context(): + self.rectangularWall(ow, h, edges="hFFH", move="right") + self.rectangularWall(ow, h, edges="hFFH", move="right") + self.rectangularWall(ow, h, edges="hFFH", move="up only") + + # Motor block + mw = 40 + self.rectangularWall(3.6 * d, h, edges=["h", "f", MotorEdge(self, None),"f"], callback=[self.mainPlate], move="up") + self.rectangularWall(3.6 * d, h, edges=["h", "f", MotorEdge(self, None),"f"], callback=[self.frontPlate], move="up") + self.rectangularWall(3.6 * d, mw, edges="ffff", move="up") + with self.saved_context(): + self.rectangularWall(mw, h, edges="hFeH", move="right") + self.rectangularWall(mw, h, edges="hFeH", move="right") + + self.pulley(88, "GT2_2mm", r_axle=a / 2.0, move="right") + self.pulley(88, "GT2_2mm", r_axle=a / 2.0, move="right") + self.rectangularWall(mw, h, edges="hFeH", move="up only") + self.axle = 19 + + for i in range(3): + self.parts.disc(self.diameter - 2 * self.rubberthickness, + hole=self.axle, move="right") + self.parts.disc(self.diameter - 2 * self.rubberthickness, + hole=self.axle, move="up right") + + for i in range(3): + self.parts.disc(self.diameter - 2 * self.rubberthickness, + hole=self.axle, move="left") + self.parts.disc(self.diameter - 2 * self.rubberthickness, + hole=self.axle, move="left up") + + for i in range(3): + self.parts.disc(self.diameter - 2 * self.rubberthickness + 4, + hole=self.axle, move="right") + self.parts.disc(self.diameter - 2 * self.rubberthickness + 4, + hole=self.axle, move="right up")
+ + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/roundedbox.html b/html/_modules/boxes/generators/roundedbox.html new file mode 100644 index 0000000..83611f5 --- /dev/null +++ b/html/_modules/boxes/generators/roundedbox.html @@ -0,0 +1,229 @@ + + + + + + + + boxes.generators.roundedbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.roundedbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import boxes
+
+
+
[docs]class RoundedBox(boxes.Boxes): + """Box with vertical edges rounded""" + + description = """ +Default: edge_style = f Finger Joint: +![Finger Joint](static/samples/RoundedBox-2.jpg) + +Alternative: edge_style = h Edge (parallel Finger Joint Holes): +![Finger Joint Holes](static/samples/RoundedBox-3.jpg) + +With lid: +""" + + ui_group = "FlexBox" + + def __init__(self): + boxes.Boxes.__init__(self) + self.addSettingsArgs(boxes.edges.FingerJointSettings) + self.addSettingsArgs(boxes.edges.DoveTailSettings) + self.addSettingsArgs(boxes.edges.FlexSettings) + self.buildArgParser("x", "y", "outside", sh="100.0") + self.argparser.add_argument( + "--radius", action="store", type=float, default=15, + help="Radius of the corners in mm") + self.argparser.add_argument( + "--wallpieces", action="store", type=int, default=1, + choices=[1, 2, 3, 4], help="number of pieces for outer wall") + self.argparser.add_argument( + "--edge_style", action="store", + type=boxes.ArgparseEdgeType("fFh"), choices=list("fFh"), + default="f", + help="edge type for top and bottom edges") + self.argparser.add_argument( + "--top", action="store", type=str, default="none", + choices=["closed", "hole", "lid",], + help="style of the top and lid") + + def hole(self): + t = self.thickness + x, y, r = self.x, self.y, self.radius + + dr = 2*t + if self.edge_style == "h": + dr = t + + if r > dr: + r -= dr + else: + x += dr - 2*r + y += dr - 2*r + self.moveTo(dr-r, 0) + r = 0 + + lx = x - 2*r - 2*dr + ly = y - 2*r - 2*dr + + self.moveTo(0, dr) + for l in (lx, ly, lx, ly): + self.edge(l); + self.corner(90, r) + + def cb(self, nr): + h = 0.5 * self.thickness + + left, l, right = self.surroundingWallPiece(nr, self.x, self.y, self.radius, self.wallpieces) + for dh in self.sh[:-1]: + h += dh + self.fingerHolesAt(0, h, l, 0) + + def render(self): + + x, y, sh, r = self.x, self.y, self.sh, self.radius + + if self.outside: + self.x = x = self.adjustSize(x) + self.y = y = self.adjustSize(y) + self.sh = sh = self.adjustSize(sh) + + r = self.radius = min(r, y / 2.0) + + t = self.thickness + + h = sum(sh) + t * (len(sh) - 1) + es = self.edge_style + + corner_holes = True + if self.edge_style == "f": + pe = "F" + ec = False + elif self.edge_style == "F": + pe = "f" + ec = False + else: # "h" + pe = "f" + corner_holes = True + ec = True + + with self.saved_context(): + self.roundedPlate(x, y, r, es, wallpieces=self.wallpieces, + extend_corners=ec, move="right") + for dh in self.sh[:-1]: + self.roundedPlate(x, y, r, "f", wallpieces=self.wallpieces, + extend_corners=False, move="right") + self.roundedPlate(x, y, r, es, wallpieces=self.wallpieces, + extend_corners=ec, move="right", + callback=[self.hole] if self.top != "closed" else None) + if self.top == "lid": + r_extra = self.edges[self.edge_style].spacing() + self.roundedPlate(x+2*r_extra, + y+2*r_extra, + r+r_extra, + "e", wallpieces=self.wallpieces, + extend_corners=False, move="right") + + self.roundedPlate(x, y, r, es, wallpieces=self.wallpieces, move="up only") + + self.surroundingWall(x, y, r, h, pe, pe, pieces=self.wallpieces, + callback=self.cb)
+ + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/royalgame.html b/html/_modules/boxes/generators/royalgame.html new file mode 100644 index 0000000..3445024 --- /dev/null +++ b/html/_modules/boxes/generators/royalgame.html @@ -0,0 +1,277 @@ + + + + + + + + boxes.generators.royalgame — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.royalgame

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class RoyalGame(Boxes): + """The Royal Game of Ur""" + + ui_group = "Misc" + + description = """Most of the blue lines need to be engraved by cutting with high speed and low power. But there are three blue holes that actually need to be cut: The grip hole in the lid and two tiny rectangles on the top and bottom for the lid to grip into. + +![Lid Details](static/samples/RoyalGame-2.jpg) + +![All pieces](static/samples/RoyalGame-3.jpg) + +""" + + def __init__(self): + Boxes.__init__(self) + + + self.addSettingsArgs(edges.FingerJointSettings) + self.buildArgParser(x=200) + + def dice(self, size, num=1, move=None): + + s = size + r = s / 20.0 + dr = r * 2 + h = s/2*3**0.5 + t = self.thickness + tw, th = (num + 0.5) * size, size + + if self.move(tw, th, move, True): + return + + self.moveTo(r, 0) + for i in range(2*num): + self.polyline((s-t)/2-dr, 90, h/2-r, -90, t, -90, h/2-r, 90, (s-t)/2-dr, (120, r), s-2*dr, (120, r), s-2*dr, (120, r)) + self.ctx.stroke() + if i % 2: + self.moveTo(.5*s - 2*dr, s, 180) + else: + self.moveTo(1.5*s -2*dr, s, 180) + + self.move(tw, th, move) + + def five(self, x, y, s): + self.hole(x, y, 0.05*s) + self.hole(x, y, 0.12*s) + for dx in (-1, 1): + for dy in (-1, 1): + self.hole(x+dx*.25*s, y+dy*.25*s, 0.05*s) + self.hole(x+dx*.25*s, y+dy*.25*s, 0.12*s) + + @restore + @holeCol + def _castle(self, x, y, s): + l = s/7*2**0.5 + self.moveTo(x-s/2 + s/14, y-s/2, 45) + self.polyline(*([l, -90, l, 90]*3 + [l/2, 90])*4) + + def castle(self, x, y, s): + self._castle(x, y, 0.9*s) + self._castle(x, y, 0.5*s) + self.five(x, y, 0.4*s) + + def castles(self, x, y, s): + for dx in (-1, 1): + for dy in (-1, 1): + self._castle(x+dx*0.25*s, y+dy*0.25*s, 0.4*s) + self.five(x+dx*0.25*s, y+dy*0.25*s, 0.3*s) + + @restore + @holeCol + def rosette(self, x, y, s): + self.moveTo(x, y, 22.5) + with self.saved_context(): + self.moveTo(0.1*s, 0, -30) + for i in range(8): + self.polyline(0, (60, 0.35*s), 0, 120, 0, (60, 0.35*s), 0, + -120, 0, (45, 0.1*s), 0, -120) + self.moveTo(0, 0, -22.5) + self.moveTo(0.175*s, 0) + for i in range(8): + self.polyline(0, (67.5, 0.32*s), 0, 90, 0, (67.5, 0.32*s), 0, -180) + + @holeCol + def eyes(self, x, y, s): + for dx in (-1, 1): + for dy in (-1, 1): + posx = x+dx*0.3*s + posy = y+dy*0.25*s + self.rectangularHole(posx, posy, 0.4*s, 0.5*s) + self.hole(posx, posy, 0.05*s) + with self.saved_context(): + self.moveTo(posx, posy-0.2*s, 60) + self.corner(60, 0.4*s) + self.corner(120) + self.corner(60, 0.4*s) + self.corner(120) + self.moveTo(0, 0, -60) + self.moveTo(0, -0.05*s, 60) + self.corner(60, 0.5*s) + self.corner(120) + self.corner(60, 0.5*s) + + for i in range(4): + self.rectangularHole(x, y + (i-1.5)*s*0.25, 0.12*s, 0.12*s) + + def race(self, x, y, s): + for dx in range(4): + for dy in range(4): + posx = (dx-1.5) * s / 4.5 + x + posy = (dy-1.5) * s / 4.5 + y + self.rectangularHole(posx, posy, s/5, s/5) + if dx in (1, 2) and dy in (0,3): + continue + self.hole(posx, posy, s/20) + + def top(self): + + patterns = [ + [self.castle, self.rosette, None, None, self.eyes, self.five, self.eyes, self.rosette], + [self.five, self.eyes, self.castles, self.five, self.rosette, self.castles, self.five, self.race]] + + s = self.size + for x in range(8): + for y in range(3): + if x in [2, 3] and y != 1: + continue + posx = (0.5+x) * s + posy = (0.5+y) * s + self.rectangularHole(posx, posy, 0.9*s, 0.9*s) + pattern = patterns[y % 2][x] + if pattern: + pattern(posx, posy, 0.9*s) + + def player1(self): + for i in range(3): + self.hole(0, 0, r=self.size * (i+2) / 12) + + def player2(self, x=0, y=0): + s = self.size + self.hole(x, y, 0.07*s) + for dx in (-1, 1): + for dy in (-1, 1): + self.hole(x+dx*.2*s, y+dy*.2*s, 0.07*s) + + def render(self): + + x = self.x + t = self.thickness + self.size = size = x / 8.0 + h = size/2 * 3**0.5 + y = 3 * size + + self.rectangularWall(x, h, "FLFF", move="right") + self.rectangularWall(y, h, "nlmE", callback=[ + lambda:self.hole(y/2, h/2, d=0.6*h)], move="up") + self.rectangularWall(y, h, "FfFf") + self.rectangularWall(x, h, "FeFF", move="left up") + + self.rectangularWall(x, y, "fMff", move="up") + self.rectangularWall(x, y, "fNff", callback=[self.top,], move="up") + + + self.partsMatrix(7, 7, "up", self.parts.disc, 0.8*size, callback=self.player1) + self.partsMatrix(7, 7, "up", self.parts.disc, 0.8*size, callback=self.player2) + + self.dice(size, 4, move="up") + self.dice(size, 4, move="up")
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/shutterbox.html b/html/_modules/boxes/generators/shutterbox.html new file mode 100644 index 0000000..3043bdc --- /dev/null +++ b/html/_modules/boxes/generators/shutterbox.html @@ -0,0 +1,322 @@ + + + + + + + + boxes.generators.shutterbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.shutterbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class ShutterBox(Boxes): + """Box with a rolling shutter made of flex""" + + ui_group = "FlexBox" + + description = """Beware of the rolling shutter effect! Use wax on sliding surfaces. + +![Inside](static/samples/ShutterBox-3.jpg) + +![Detail](static/samples/ShutterBox-2.jpg) +""" + + def side(self, l, h, r, style, move=None): + t = self.thickness + + if self.move(l+2*t, h+2*t, move, True): + return + + self.moveTo(t, t) + + self.ctx.save() + + n = self.n + a = 90. / n + ls = 2*math.sin(math.radians(a/2)) * (r-2.5*t) + + #self.hole(l-r, r, r-2.5*t) + if style == "double": + #self.hole(r, r, r-2.5*t) + self.ctx.save() + self.fingerHolesAt(r, 2*t, l-2*r, 0) + self.moveTo(r, 2.5*t, 180 - a/2) + for i in range(n): + self.fingerHolesAt(0, 0.5*t, ls, 0) + self.moveTo(ls, 0, -a) + if h - 2*r > 2*t: + self.moveTo(0, 0, a/2) + self.fingerHolesAt(0, 0.5*t, h - 2*r, 0) + self.ctx.restore() + else: + self.fingerHolesAt(0, 2*t, l-r, 0) + self.moveTo(l-r, 2.5*t, a/2) + for i in range(n): + self.fingerHolesAt(0, -0.5*t, ls, 0) + self.moveTo(ls, 0, a) + if h - 2*r > 2*t: + self.moveTo(0, 0, -a/2) + self.fingerHolesAt(0, -0.5*t, h - 2*r, 0) + self.ctx.restore() + + self.edges["f"](l) + self.corner(90) + self.edges["f"](h-r) + self.polyline(0, -90, t, 90, 0, (90, r+t)) + if style == "single": + self.polyline(l-r, 90, t) + self.edges["f"](h) + else: + self.polyline(l-2*r, (90, r+t), 0, 90, t, -90) + self.edges["f"](h-r) + + self.move(l+2*t, h+2*t, move) + + def cornerRadius(self, r, two=False, move=None): + s = self.spacing + if self.move(r, r+s, move, True): + return + for i in range(2 if two else 1): + self.polyline(r, 90, r, 180, 0, (-90, r), 0 ,-180) + self.moveTo(r, r+s, 180) + self.move(r, r+s, move) + + def rails(self, l, r, move=None): + t = self.thickness + s = self.spacing + tw, th = l+2.5*t+3*s, r+1.5*t+3*s + + if self.move(tw, th, move, True): + return + + self.moveTo(2.5*t+s, 0) + self.polyline(l-r, (90, r+t), 0, 90, t, 90, 0, (-90, r), l-r, 90, t, 90) + self.moveTo(-t-s, t+s) + self.polyline(l-r, (90, r+t), 0, 90, t, 90, 0, (-90, r), l-r, 90, t, 90) + self.moveTo(0.5*t, t+s) + self.polyline(l-r, (90, r-1.5*t), 0, 90, t, 90, 0, (-90, r-2.5*t), l-r, 90, t, 90) + self.moveTo(-t-s, t+s) + self.polyline(l-r, (90, r-1.5*t), 0, 90, t, 90, 0, (-90, r-2.5*t), l-r, 90, t, 90) + + self.move(tw, th, move) + + def rails2(self, l, r, move=None): + t = self.thickness + s = self.spacing + tw, th = l+2.5*t+3*s, 2*r+t + + if self.move(tw, th, move, True): + return + + self.moveTo(r+t, 0) + for i in range(2): + self.polyline(l-2*r, (90, r+t), 0, 90, t, 90, 0, (-90, r), l-2*r, + (-90, r), 0, 90, t, 90, 0, (90, r+t)) + self.moveTo(0, 1.5*t) + self.polyline(l-2*r, (90, r-1.5*t), 0, 90, t, 90, 0, (-90, r-2.5*t), l-2*r, + (-90, r-2.5*t), 0, 90, t, 90, 0, (90, r-1.5*t)) + self.moveTo(0, r) + + self.move(tw, th, move) + + + def door(self, l, h, move=None): + t = self.thickness + if self.move(l, h, move, True): + return + self.fingerHolesAt(t, t, h-2*t) + self.edge(2*t) + self.edges["X"](l-2*t, h) + self.polyline(0, 90, h, 90, l, 90, h, 90) + self.move(l, h, move) + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings, surroundingspaces=0.5) + self.addSettingsArgs(edges.FlexSettings, distance=.75, connection=2.) + + self.buildArgParser(x=150, y=100, h=100) + self.argparser.add_argument( + "--radius", action="store", type=float, default=40.0, + help="radius of the corners") + self.argparser.add_argument( + "--style", action="store", type=str, default="single", + choices=["single", "double"], + help="Number of rounded top corners") + + + def render(self): + x, y, h, r = self.x, self.y, self.h, self.radius + style = self.style + + self.n = n = 3 + + if not r: + self.radius = r = h / 2 + self.radius = r = min(r, h/2) + + t = self.thickness + self.ctx.save() + self.side(x, h, r, style, move="right") + self.side(x, h, r, style, move="right") + if style == "single": + self.rectangularWall(y, h, "fFEF", move="right") + else: + self.rectangularWall(y, h-r, "fFeF", move="right") + self.rectangularWall(y, h-r, "fFeF", move="right") + + if style == "double": + self.cornerRadius(r, two=True, move="right") + + self.cornerRadius(r, two=True, move="right") + if style == "single": + self.rails(x, r, move="right") + else: + self.rails2(x, r, move="right") + + self.ctx.restore() + self.side(x, h, r, style, move="up only") + + self.rectangularWall(x, y, "FFFF", move="right") + + if style == "single": + self.door(x-r+0.5*math.pi*r + 3*t, y-0.2*t, move="right") + else: + self.door(x-2*r+math.pi*r + 3*t, y-0.2*t, move="right") + + self.rectangularWall(2*t, y-2.2*t, edges="eeef", move="right") + + + a = 90. / n + ls = 2*math.sin(math.radians(a/2)) * (r-2.5*t) + + edges.FingerJointSettings(t, angle=a).edgeObjects(self, chars="aA") + edges.FingerJointSettings(t, angle=a/2).edgeObjects(self, chars="bB") + + + if style == "double": + if h - 2*r > 2*t: + self.rectangularWall(h - 2*r, y, "fBfe", move="right") + self.rectangularWall(ls, y, "fAfb", move="right") + else: + self.rectangularWall(ls, y, "fAfe", move="right") + + for i in range(n-2): + self.rectangularWall(ls, y, "fAfa", move="right") + + self.rectangularWall(ls, y, "fBfa", move="right") + + self.rectangularWall(x-2*r, y, "fbfb", move="right") + else: + self.rectangularWall(x-r, y, "fbfe", move="right") + + self.rectangularWall(ls, y, "fafB", move="right") + + for i in range(n-2): + self.rectangularWall(ls, y, "fafA", move="right") + + + if h - 2*r > 2*t: + self.rectangularWall(ls, y, "fbfA", move="right") + self.rectangularWall(h - 2*r, y, "fefB", move="right") + else: + self.rectangularWall(ls, y, "fefA", move="right")
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/sidedoorhousing.html b/html/_modules/boxes/generators/sidedoorhousing.html new file mode 100644 index 0000000..2cff2ee --- /dev/null +++ b/html/_modules/boxes/generators/sidedoorhousing.html @@ -0,0 +1,200 @@ + + + + + + + + boxes.generators.sidedoorhousing — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.sidedoorhousing

+#!/usr/bin/env python3
+# Copyright (C) 2013-2020 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes.generators.console2 import Console2
+
+
[docs]class SideDoorHousing(Console2): + """A box with service hatches at the sides""" + + ui_group = "Box" + + description = """ +This box is designed as a housing for electronic projects. It has hatches that can be re-opened with simple tools. It intentionally cannot be opened with bare hands - if build with thin enough material. The hatches are at the x sides. + +#### Assembly instructions +The main body is easy to assemble by starting with the floor and then adding the four walls and the top piece. + +For the removable walls you need to add the lips and latches. The U-shaped clamps holding the latches in place need to be clued in place without also gluing the latches themselves. Make sure the springs on the latches point inwards and the angled ends point to the side walls as shown here (showing a different box type): + +![Wall details](static/samples/Console2-backwall-detail.jpg) + +#### Re-Opening + +The latches lock in place when closed. To open them they need to be pressed in and can then be moved aside. +""" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings, surroundingspaces=.5) + self.addSettingsArgs(edges.StackableSettings) + + self.buildArgParser(x=100, y=100, h=100, bottom_edge="s") + self.argparser.add_argument( + "--double_door", action="store", type=boolarg, default=True, + help="allow removing the backwall, too") + + + def render(self): + x, y, h = self.x, self.y, self.h + t = self.thickness + bottom = self.edges.get(self.bottom_edge) + + self.latchpos = latchpos = 3*t + + + self.rectangularWall(x, y, "ffff", move="right") # floor + if self.double_door: # top + self.rectangularWall(x, y, "EFEF", move="right") + else: + self.rectangularWall(x, y, "EFFF", move="right") + + for move in ("right", "mirror right"): + re = edges.CompoundEdge(self, ("f", "e"), + (bottom.endwidth()+t, h-t)) + if self.double_door: + le = edges.CompoundEdge(self, ("e", "f"), + (h-t, bottom.endwidth()+t)) + else: + le = "f" + self.rectangularWall( # side + y, h, (bottom, re, "f", le), ignore_widths=[1, 6], + callback=[ + None, None, lambda: + (self.rectangularHole(1.55*t, latchpos, 1.1*t, 1.1*t), + self.double_door and + self.rectangularHole(y-1.55*t, latchpos, 1.1*t, 1.1*t))], + move=move) + + + for i in range(2 if self.double_door else 1): + self.rectangularWall(x, t, (bottom, "F", "e", "F"), + ignore_widths=[1, 6], move="up") + self.rectangularWall( # back wall + x, h-1.1*t, "eEeE", + callback=[ + lambda: self.fingerHolesAt(.5*t, 0, h-4.05*t-latchpos), + lambda:self.latch_hole(h-1.2*t-latchpos), + lambda: self.fingerHolesAt(.5*t, 3.05*t+latchpos, h-4.05*t-latchpos), + lambda:self.latch_hole(latchpos)], + move="right") + self.rectangularWall(x, t, (bottom, "F", "e", "F"), + ignore_widths=[1, 6], move="down only") + if not self.double_door: + self.rectangularWall(x, h, (bottom, "F", "f", "F"), + ignore_widths=[1, 6], move="right") + + # hardware for back wall + if self.double_door: + latches = 4 + else: + latches = 2 + + self.partsMatrix(latches, 0, "right", + self.rectangularWall, 2*t, h-4.05*t-latchpos, "EeEf") + self.partsMatrix(latches, 2, "up", self.latch) + self.partsMatrix(2*latches, 2, "up", self.latch_clamp)
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/silverwarebox.html b/html/_modules/boxes/generators/silverwarebox.html new file mode 100644 index 0000000..ad6ba73 --- /dev/null +++ b/html/_modules/boxes/generators/silverwarebox.html @@ -0,0 +1,199 @@ + + + + + + + + boxes.generators.silverwarebox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.silverwarebox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import Boxes, restore
+
+
+
[docs]class Silverware(Boxes): + """ + Cuttlery stand with carrying grip + using flex for rounded corners + """ + + ui_group = "Unstable" + + + def __init__(self): + Boxes.__init__(self) + + self.buildArgParser(x=250, y=154, h=120) + + self.argparser.add_argument( + "--cornerradius", action="store", type=int, default=30, + help="Radius of the corners") + self.argparser.add_argument( + "--handleheight", action="store", type=int, default=150, + help="Height of the handle") + self.argparser.add_argument( + "--handlewidth", action="store", type=int, default=120, + help="Width of the handle") + + + #################################################################### + ### Parts + #################################################################### + + def basePlate(self, x, y, r): + self.roundedPlate(x, y, r, extend_corners=False, callback=[ + lambda: self.fingerHolesAt(x / 3.0 - r, 0, 0.5 * (y - self.thickness)), + lambda: self.fingerHolesAt(x / 6.0, 0, 0.5 * (y - self.thickness)), + lambda: self.fingerHolesAt(y / 2.0 - r, 0, x), + lambda: self.fingerHolesAt(x / 2.0 - r, 0, 0.5 * (y - self.thickness)) + ]) + + def wall(self, x=100, y=100, h=100, r=0): + self.surroundingWall(x, y, r, h, top="E", bottom='h', callback={ + 0: lambda: self.fingerHolesAt(x / 6.0, 0, h - 10), + 4: lambda: self.fingerHolesAt(x / 3.0 - r, 0, h - 10), + 1: lambda: self.fingerHolesAt(y / 2.0 - r, 0, h - 10), + 3: lambda: self.fingerHolesAt(y / 2.0 - r, 0, h - 10), + 2: lambda: self.fingerHolesAt(x / 2.0 - r, 0, h - 10), + }, + move="up") + + @restore + def centerWall(self, x, h): + self.moveTo(self.edges["f"].spacing(), self.edges["f"].spacing()) + for i in range(2, 5): + self.fingerHolesAt(i * x / 6.0, 0, h - 10) + + self.edges["f"](x) + self.corner(90) + self.edges["f"](h - 10) + self.corner(90) + + self.handle(x, self.handleheight, self.handlewidth) + + self.corner(90) + self.edges["f"](h - 10) + self.corner(90) + self.ctx.stroke() + + ################################################## + ### main + ################################################## + + def render(self): + x = self.x + y = self.y + h = self.h + r = self.cornerradius + + t = self.thickness + b = self.burn + + self.wall(x, y, h, r) + self.centerWall(x, h) + self.moveTo(x + 2 * self.edges["f"].spacing()) + + l = (y - t) / 2.0 + + for _ in range(3): + self.rectangularWall(l, h - 10, edges="ffef", move="right") + + self.moveTo(-3.0 * (l + 2 * t + 8 * b), h - 10 + 2 * t + 8 * b) + self.basePlate(x, y, r)
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/slidingdrawer.html b/html/_modules/boxes/generators/slidingdrawer.html new file mode 100644 index 0000000..fcc342e --- /dev/null +++ b/html/_modules/boxes/generators/slidingdrawer.html @@ -0,0 +1,140 @@ + + + + + + + + boxes.generators.slidingdrawer — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.slidingdrawer

+from boxes import *
+
+
[docs]class SlidingDrawer(Boxes): + """Sliding drawer box""" + + ui_group = "Box" + + def __init__(self): + Boxes.__init__(self) + self.buildArgParser(x=60, y=100, h=30, outside='true') + self.addSettingsArgs(edges.FingerJointSettings, finger=2.0, space=2.0) + self.addSettingsArgs(edges.GroovedSettings, width=0.4) + + self.argparser.add_argument( + "--play", action="store", type=float, default=0.15, + help="play between the two parts as multipleof the wall thickness") + + def render(self): + + x, y, h = self.x, self.y, self.h + x = self.adjustSize(x) + y = self.adjustSize(y) + h = self.adjustSize(h) + + t = self.thickness + p = self.play * t + + y = y + t + if not self.outside: + x = x + 4*t+ 2*p + y = y + 3*t+ 2*p + h = h + 3*t+ 2*p + + x2 = x - (2*t + 2*p) + y2 = y - (2*t + 2*p) + h2 = h - (t + 2*p) + + self.rectangularWall(x2, h2, "FFzF", label="in box wall", move="right") + self.rectangularWall(y2, h2, "ffef", label="in box wall", move="up") + self.rectangularWall(y2, h2, "ffef", label="in box wall") + self.rectangularWall(x2, h2, "FFeF", label="in box wall", move="left up") + self.rectangularWall(y2, x2, "FfFf", label="in box bottom", move="up") + + self.rectangularWall(y, x, "FFFe", label="out box bottom", move="right") + self.rectangularWall(y, x, "FFFe", label="out box top", move="up") + self.rectangularWall(y, h, "fffe", label="out box wall") + self.rectangularWall(y, h, "fffe", label="out box wall", move="up left") + + self.rectangularWall(x, h, "fFfF", label="out box wall")
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/spicesrack.html b/html/_modules/boxes/generators/spicesrack.html new file mode 100644 index 0000000..c590efb --- /dev/null +++ b/html/_modules/boxes/generators/spicesrack.html @@ -0,0 +1,264 @@ + + + + + + + + boxes.generators.spicesrack — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.spicesrack

+#!/usr/bin/env python3
+# Copyright (C) 2013-2019 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+class FrontEdge(edges.Edge):
+
+    def __call__(self, length, **kw):
+        with self.saved_context():
+            a = 90
+            r = (self.diameter +self.space) / 2
+            self.ctx.scale(1, self.edge_width/r)
+            for i in range(self.numx):
+                self.corner(-a)
+                self.corner(180, r)
+                self.corner(-a)
+        self.moveTo(length)
+
+    def margin(self):
+        return self.edge_width
+                
+
[docs]class SpicesRack(Boxes): + """Rack for cans of spices""" + + ui_group = "Shelf" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings, surroundingspaces=1.0) + + self.argparser.add_argument( + "--diameter", action="store", type=float, default=55., + help="diameter of spice cans") + + self.argparser.add_argument( + "--height", action="store", type=float, default=60., + help="height of the cans that needs to be supported") + self.argparser.add_argument( + "--space", action="store", type=float, default=10., + help="space between cans") + self.argparser.add_argument( + "--numx", action="store", type=int, default=5, + help="number of cans in a row") + self.argparser.add_argument( + "--numy", action="store", type=int, default=6, + help="number of cans in a column") + self.argparser.add_argument( + "--in_place_supports", action="store", type=boolarg, default=False, + help="place supports pieces in holes (check for fit yourself)") + self.argparser.add_argument( + "--feet", action="store", type=boolarg, default=False, + help="add feet so the rack can stand on the ground") + + + def support(self, width, height, move=None): + t = self.thickness + tw = width + t + th = height + + r = min(width - 2*t, height - 2*t) + + if self.move(tw, th, move, True): + return + + self.polyline(width-r, 90, 0, (-90, r), 0, 90, height-r, 90, width, 90) + self.edges["f"](height) + + self.move(tw, th, move) + + def foot(self, width, height, move=None): + t = self.thickness + tw, th = height, width + t + + if self.move(tw, th, move, True): + return + + self.moveTo(0, t) + self.edges["f"](height) + self.polyline(0, 90, width, 90, 0, (90, height), width-height, 90) + + self.move(tw, th, move) + + def holes(self): + w = 2* self.base_r + r = self.diameter / 2 + a = self.base_angle + l = self.hole_length + self.moveTo(0, self.hole_distance) + + with self.saved_context(): + self.ctx.scale(1, l/self.base_h) + self.moveTo(self.space/2, 0, 90) + for i in range(self.numx): + self.polyline(0, -a, 0, (-180+2*a, r), 0, -90-a, w, -90) + self.moveTo(0, -(self.diameter+self.space)) + self.ctx.stroke() + if self.feet and not self.feet_done: + self.feet_done = True + return + + if not self.in_place_supports: + return + inner_width = self.hole_distance + self.hole_length/3 + t = self.thickness + for i in range(self.numx-1): + with self.saved_context(): + self.moveTo((self.diameter+self.space)*(i+0.5)- (inner_width+t)/2, self.spacing) + self.support(inner_width, (self.h-t)/2) + + def backCB(self): + t = self.thickness + dy = self.h/2 - t/2 + for i in range(self.numy): + self.fingerHolesAt(0, (i+1)*self.h-0.5*self.thickness-dy, self.x, 0) + for j in range(1, self.numx): + self.fingerHolesAt( + j*(self.diameter+self.space), + (i+1)*self.h-t-dy, (self.h-t)/2, -90) + + def render(self): + self.feet_done = False + t = self.thickness + self.x = x = self.numx * (self.diameter+self.space) + d = self.diameter + + self.base_angle = 10 + self.base_r = self.diameter/2 * math.cos(math.radians(self.base_angle)) + self.base_h = self.diameter/2 * (1-math.sin(math.radians(self.base_angle))) + self.angle = math.degrees(math.atan(self.base_r/self.height)) + self.hole_length = (self.base_h**2+self.height**2)**0.5 + self.hole_distance = (self.diameter-self.base_r) * math.sin(math.radians(self.angle)) + + self.h = (self.space + d) / math.cos(math.radians(self.angle)) + h = self.numy * self.h - self.h / 2 + 6*t + + width = self.hole_distance + self.hole_length + self.space/2 + inner_width = self.hole_distance + self.hole_length/3 + + self.edge_width = width - inner_width + + for i in range(self.numy): + self.rectangularWall(x, inner_width,[ + "f", "e", FrontEdge(self, self), "e"], + callback=[self.holes], move="up") + + self.rectangularWall(x, h, + callback=[self.backCB, + None, + lambda:self.hole(3*t, 3*t, 1.5), + lambda:self.hole(3*t, 3*t, 1.5), + ], move="up") + + + if not self.in_place_supports: + self.partsMatrix((self.numx-1)*self.numy, self.numx-1, "up", + self.support, inner_width, (self.h-t)/2) + if self.feet: + self.partsMatrix(self.numx-1, self.numx-1, "up", + self.foot, width, (self.h-t)/2)
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/stachel.html b/html/_modules/boxes/generators/stachel.html new file mode 100644 index 0000000..64a8abf --- /dev/null +++ b/html/_modules/boxes/generators/stachel.html @@ -0,0 +1,188 @@ + + + + + + + + boxes.generators.stachel — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.stachel

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+import math
+
+
[docs]class Stachel(Boxes): + """Bass Recorder Endpin""" + + ui_group = "Misc" + + def __init__(self): + Boxes.__init__(self) + + self.argparser.add_argument( + "--flutediameter", action="store", type=float, default=115.0, + help="diameter of the flutes bottom in mm") + self.argparser.add_argument( + "--polediameter", action="store", type=float, default=25., + help="diameter if the pin in mm") + self.argparser.add_argument( + "--wall", action="store", type=float, default=7., + help="width of the surrounding wall in mm") + + def layer(self, ri, ro, rp, holes=False, move=""): + + r = 2.5 # radius + l = 25 # depth of clamp + w = 20 # width of clamp + + wp = rp+8 # width pole + + tw = 2*ro + 2*rp + th = 2*ro + l + + if self.move(tw, th, move, True): + return + + self.moveTo(ro, r, 90) + + a1 = math.degrees(math.asin(w / ro)) + a2 = math.degrees(math.asin(wp / ro)) + l1 = ro*(1-math.cos(math.radians(a1))) + a3 = math.degrees(math.asin(1./rp)) + self.polyline(ro-ri+l-r, 90, 0, (-355, ri), 0, 90, ro-ri+l-r, # inside + (90, r), w-2*r, (90, r)) + if holes: # right side main clamp + poly1 = [(l+l1-2)/2-r, 90, w-2, -90, 2, -90, w-2, 90, + (l+l1-2)/2] + self.polyline(*poly1) + else: + self.polyline(l+l1-r) + self.polyline(0, -90+a1, 0 , (90-a1-a2, ro), 0, -90+a2) + if holes: + poly2 = [2*rp+15, 90, wp-2, -90, 2, -90, wp-2, 90, 10-2-r] + self.polyline(*poly2) + else: + self.polyline(25+2*rp-r) + self.polyline(0, (90, r), wp-1-r, 90, 20, 90-a3, 0, (-360+2*a3, rp), 0, 90-a3, 20, 90, wp-1-r, (90, r)) + if holes: + self.polyline(*list(reversed(poly2))) + else: + self.polyline(25+2*rp-r) + self.polyline(0, -90+a2, 0, (270-a2-a1-5, ro), 0, (-90+a1)) + if holes: # left sidemain clamp + self.polyline(*list(reversed(poly1))) + else: + self.polyline(l+l1-r) + self.polyline(0, (90, r), w-2*r, (90, r)) + + self.move(tw, th, move) + + + + def render(self): + + ri = self.flutediameter / 2.0 + ro = ri + self.wall + rp = self.polediameter / 2.0 + w = self.wall + self.layer(ri-20, ro, rp, move="up") + self.layer(ri, ro, rp, True, move="up") + self.layer(ri, ro, rp, move="up")
+ + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/storagerack.html b/html/_modules/boxes/generators/storagerack.html new file mode 100644 index 0000000..bc90d72 --- /dev/null +++ b/html/_modules/boxes/generators/storagerack.html @@ -0,0 +1,201 @@ + + + + + + + + boxes.generators.storagerack — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.storagerack

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class StorageRack(Boxes): + """StorageRack to store boxes and trays which have their own floor""" + + ui_group = "Shelf" + + description = """ + +Drawers are not included: + +![Inside](static/samples/StorageRack-2.jpg) +![Back wall details](static/samples/StorageRack-3.jpg) + +""" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.addSettingsArgs(edges.StackableSettings) + + self.argparser.add_argument( + "--depth", action="store", type=float, default=200, + help="depth of the rack") + self.argparser.add_argument( + "--rail", action="store", type=float, default=30, + help="depth of the rack") + self.buildArgParser("x", "sh", "outside", "bottom_edge") + self.argparser.add_argument( + "--top_edge", action="store", + type=ArgparseEdgeType("FheSŠ"), choices=list("FheSŠ"), + default="F", + help="edge type for top edge") + + def hHoles(self): + posh = -0.5 * self.thickness + for h in self.sh[:-1]: + posh += h + self.thickness + self.fingerHolesAt(posh, 0, self.depth) + + def backHoles(self): + posh = -0.5 * self.thickness + for nr, h in enumerate(self.sh[:-1]): + posh += h + self.thickness + if ((self.bottom_edge == "e" and nr == 0) or + (self.top_edge == "e" and nr == len(self.sh) - 2)): + self.fingerHolesAt(0, posh, self.x, 0) + else: + self.fingerHolesAt(0, posh, self.rail, 0) + self.fingerHolesAt(self.x, posh, self.rail, 180) + + def render(self): + if self.outside: + self.depth = self.adjustSize(self.depth, e2=False) + self.sh = self.adjustSize(self.sh, self.top_edge, self.bottom_edge) + self.x = self.adjustSize(self.x) + + h = sum(self.sh) + self.thickness * (len(self.sh) - 1) + x = self.x + d = self.depth + t = self.thickness + + + # outer walls + b = self.bottom_edge + t = self.top_edge + self.closedtop = self.top_edge in "fFhŠ" + + # sides + + self.ctx.save() + + # side walls + self.rectangularWall(d, h, [b, "F", t, "E"], callback=[None, self.hHoles, ], move="up") + self.rectangularWall(d, h, [b, "E", t, "F"], callback=[None, self.hHoles, ], move="up") + + # full floors + self.rectangularWall(d, x, "fffE", move="up") + self.rectangularWall(d, x, "fffE", move="up") + + num = len(self.sh)-1 + if b == "e": + num -= 1 + if t == "e": + num -= 1 + + for i in range(num): + self.rectangularWall(d, self.rail, "ffee", move="up") + self.rectangularWall(d, self.rail, "feef", move="up") + + self.ctx.restore() + self.rectangularWall(d, h, "ffff", move="right only") + + # back wall + self.rectangularWall(x, h, [b, "f", t, "f"], callback=[self.backHoles], move="up")
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/storageshelf.html b/html/_modules/boxes/generators/storageshelf.html new file mode 100644 index 0000000..bfae587 --- /dev/null +++ b/html/_modules/boxes/generators/storageshelf.html @@ -0,0 +1,237 @@ + + + + + + + + boxes.generators.storageshelf — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.storageshelf

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes.lids import _TopEdge
+
+
[docs]class StorageShelf(_TopEdge): + """StorageShelf can be used to store Typetray""" + + ui_group = "Shelf" + description = "This is a simple shelf box." + + def __init__(self): + Boxes.__init__(self) + self.addTopEdgeSettings(fingerjoint={"surroundingspaces": 0.5}, + roundedtriangle={"outset" : 1}) + self.buildArgParser("x", "sy", "sh", "outside", "bottom_edge", + "top_edge") + self.argparser.add_argument( + "--retainer", action="store", type=float, default=0.0, + help="height of retaining wall at the front edges") + self.argparser.add_argument( + "--retainer_hole_edge", action="store", type=boolarg, default=False, + help="use finger hole edge for retainer walls") + + + + def ySlots(self): + posy = -0.5 * self.thickness + h = sum(self.sh) + self.thickness * (len(self.sh) - 1) + for y in self.sy[:-1]: + posy += y + self.thickness + self.fingerHolesAt(posy, 0, h, 90) + + def hSlots(self): + posh = -0.5 * self.thickness + for h in self.sh[:-1]: + posh += h + self.thickness + posy = 0 + for y in reversed(self.sy): + self.fingerHolesAt(posh, posy, y) + posy += y + self.thickness + + def yHoles(self): + posy = -0.5 * self.thickness + for y in self.sy[:-1]: + posy += y + self.thickness + self.fingerHolesAt(posy, 0, self.x) + + def hHoles(self): + posh = -0.5 * self.thickness + for h in self.sh[:-1]: + posh += h + self.thickness + self.fingerHolesAt(posh, 0, self.x) + + def render(self): + if self.outside: + self.sy = self.adjustSize(self.sy) + self.sh = self.adjustSize(self.sh, self.top_edge, self.bottom_edge) + self.x = self.adjustSize(self.x, e2=False) + + y = sum(self.sy) + self.thickness * (len(self.sy) - 1) + h = sum(self.sh) + self.thickness * (len(self.sh) - 1) + x = self.x + t = self.thickness + + + # outer walls + b = self.bottom_edge + t1, t2, t3, t4 = self.topEdges(self.top_edge) + #if top_edge is t put the handle on the x walls + if(self.top_edge=='t'): + t1,t2,t3,t4=(t2,t1,t4,t3) + self.closedtop = self.top_edge in "fFhŠY" + + # x sides + + self.ctx.save() + + # outer walls + # XXX retainer + self.rectangularWall(x, h, [b, "F", t1, "e"], callback=[None, self.hHoles, ], move="up", label="left") + self.rectangularWall(x, h, [b, "e", t3, "F"], callback=[None, self.hHoles, ], move="up", label="right") + + # floor + if b != "e": + e = "fffe" + if self.retainer: + e = "ffff" + self.rectangularWall(x, y, e, callback=[None, self.yHoles], move="up", label="bottom") + + # inner walls + + be = "f" if b != "e" else "e" + + for i in range(len(self.sh) - 1): + e = ["f", edges.SlottedEdge(self, self.sy[::-1], "f", slots=0.5 * x), "f", "e"] + if self.retainer: + e[3] = "f" + + self.rectangularWall(x, y, e, move="up", label="inner horizontal " + str(i+1)) + + # top / lid + if self.closedtop: + e = "FFFe" if self.top_edge == "f" else "fffe" + self.rectangularWall(x, y, e, callback=[None, self.yHoles, ], move="up", label="top") + else: + self.drawLid(x, y, self.top_edge) + + self.ctx.restore() + self.rectangularWall(x, h, "ffff", move="right only", label="invisible") + + # y walls + + # outer walls + self.rectangularWall(y, h, [b, "f", t2, "f"], callback=[self.ySlots, self.hSlots,], move="up", label="back") + + # inner walls + for i in range(len(self.sy) - 1): + # XXX retainer + e = [be, edges.SlottedEdge(self, self.sh, "e", slots=0.5 * x), + "e", "f"] + if self.closedtop: + e = [be, edges.SlottedEdge(self, self.sh, "e", slots=0.5 * x),"f", "f"] + self.rectangularWall(x, h, e, move="up", label="inner vertical " + str(i+1)) + + + if self.retainer: + for i in range(len(self.sh)): + # XXX finger holes, F edges, left and right + e = "FEeE" + if self.retainer_hole_edge or (i == 0 and b == "h"): + e = "hEeE" + self.rectangularWall(y, self.retainer, e, move="up", label="retainer " + str(i+1))
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/trafficlight.html b/html/_modules/boxes/generators/trafficlight.html new file mode 100644 index 0000000..57b8cf3 --- /dev/null +++ b/html/_modules/boxes/generators/trafficlight.html @@ -0,0 +1,299 @@ + + + + + + + + boxes.generators.trafficlight — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.trafficlight

+#!/usr/bin/env python3
+#-*- coding: utf-8 -*-
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+import math
+
+class ShadyEdge(edges.BaseEdge):
+    char = "s"
+
+    def __call__(self, lenght, **kw):
+        s = self.shades
+        h = self.h
+        a = math.atan(s/h)
+        angle = math.degrees(a)
+        for i in range(self.n):
+            self.polyline(0, -angle, h / math.cos(a), angle+90)
+            self.edges["f"](s)
+            self.corner(-90)
+            if i < self.n-1:
+                self.edge(self.thickness)
+
+    def margin(self):
+        return self.shades
+
+
[docs]class TrafficLight(Boxes): # change class name here and below + """Traffic light""" + description = u"""The traffic light was created to visualize the status of a Icinga monitored system. + +When turned by 90°, it can be also used to create a bottle holder.""" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings) + + # remove cli params you do not need + self.buildArgParser("h", "hole_dD") + # Add non default cli params if needed (see argparse std lib) + self.argparser.add_argument( + "--depth", action="store", type=float, default=100, + help="inner depth not including the shades") + self.argparser.add_argument( + "--shades", action="store", type=float, default=50, + help="depth of the shaders") + self.argparser.add_argument( + "--n", action="store", type=int, default=3, + help="number of lights") + self.argparser.add_argument( + "--upright", action="store", type=boolarg, default=True, + help="stack lights upright (or side by side)") + + def backCB(self): + t = self.thickness + for i in range(1, self.n): + self.fingerHolesAt(i*(self.h+t)-0.5*t, 0, self.h) + + def sideCB(self): + t = self.thickness + for i in range(1, self.n): + self.fingerHolesAt(i*(self.h+t)-0.5*t, 0, self.depth) + for i in range(self.n): + self.fingerHolesAt(i*(self.h+t), self.depth-2*t, self.h, 0) + + def topCB(self): + t = self.thickness + for i in range(1, self.n): + self.fingerHolesAt(i*(self.h+t)-0.5*t, 0, self.depth + self.shades) + for i in range(self.n): + self.fingerHolesAt(i*(self.h+t), self.depth-2*t, self.h, 0) + + def frontCB(self): + self.hole(self.h/2, self.h/2, self.h/2-self.thickness) + + def wall(self, h1, h2, w, edges="ffef", callback=None, move="", label=""): + edges = [self.edges.get(e, e) for e in edges] + edges += edges # append for wrapping around + overallwidth = w + edges[-1].spacing() + edges[1].spacing() + overallheight = max(h1, h2) + edges[0].spacing() + edges[2].spacing() + + if self.move(overallwidth, overallheight, move, before=True, label=label): + return + + a = math.atan((h2-h1)/float(w)) + angle = math.degrees(a) + + self.moveTo(edges[-1].spacing(), edges[0].margin()) + for i, l in [(0, w), (1, h2)]: + self.cc(callback, i, y=edges[i].startwidth() + self.burn) + edges[i](l) + self.edgeCorner(edges[i], edges[i + 1], 90) + + self.corner(angle) + self.cc(callback, i, y=edges[2].startwidth() + self.burn) + edges[2](w / math.cos(a)) + self.corner(-angle) + self.edgeCorner(edges[2], edges[2 + 1], 90) + self.cc(callback, i, y=edges[3].startwidth() + self.burn) + edges[3](h1) + self.edgeCorner(edges[3], edges[3 + 1], 90) + + self.move(overallwidth, overallheight, move, label=label) + + def addMountH(self, width, height): + ds = self.hole_dD[0] + + if len(self.hole_dD) < 2: # if no head diameter is given + dh = 0 # only a round hole is generated + y = height - max (self.thickness * 1.25, self.thickness * 1.0 + ds) # and we assume that a typical screw head diameter is twice the shaft diameter + else: + dh = self.hole_dD[1] # use given head diameter + y = height - max (self.thickness * 1.25, self.thickness * 1.0 + dh / 2) # and offset the hole to have enough space for the head + + dx = width + x1 = dx * 0.125 + x2 = dx * 0.875 + + self.mountingHole(x1, y, ds, dh, 90) + self.mountingHole(x2, y, ds, dh, 90) + + def addMountV(self, width, height): + if self.hole_dD[0] < 2 * self.burn: + return # no hole if no diameter is given + + ds = self.hole_dD[0] + + if len(self.hole_dD) < 2: # if no head diameter is given + dh = 0 # only a round hole is generated + x = max (self.thickness * 2.75, self.thickness * 2.25 + ds) # and we assume that a typical screw head diameter is twice the shaft diameter + else: + dh = self.hole_dD[1] # use given head diameter + x = max (self.thickness * 2.75, self.thickness * 2.25 + dh / 2) # and offset the hole to have enough space for the head + + dy = height + + y1 = self.thickness * 0.75 + dy * 0.125 + y2 = self.thickness * 0.75 + dy * 0.875 + + self.mountingHole(x, y1, ds, dh, 180) + self.mountingHole(x, y2, ds, dh, 180) + + def render(self): + # adjust to the variables you want in the local scope + d, h, n = self.depth, self.h, self.n + s = self.shades + t = self.thickness + + th = n * (h + t) - t + + + self.addPart(ShadyEdge(self, None)) + + # back + if self.upright: + self.rectangularWall(th, h, "FFFF", callback=[self.backCB, self.addMountV(th, h)], move="up", label="back") + else: + self.rectangularWall(th, h, "FFFF", callback=[self.backCB, self.addMountH(th, h)], move="up", label="back") + + if self.upright: + # sides + self.rectangularWall(th, d, "fFsF", callback=[self.sideCB], move="up", label="left") + self.rectangularWall(th, d, "fFsF", callback=[self.sideCB], move="up", label="right") + + # horizontal Walls / blinds tops + e = edges.CompoundEdge(self, "fF", (d, s)) + e2 = edges.CompoundEdge(self, "Ff", (s, d)) + for i in range(n): + self.rectangularWall(h, d+s, ['f', e, 'e', e2], + move="right" if i<n-1 else "right up", label="horizontal Wall " + str(i+1)) + else: + # bottom + self.rectangularWall(th, d, "fFeF", callback=[self.sideCB], + move="up", label="bottom") + # top + self.rectangularWall(th, d+s, "fFeF", callback=[self.topCB], + move="up", label="top") + # vertical walls + for i in range(n): + self.wall(d, d+s, h, move="right" if i<n-1 else "right up", label="vertical wall " + str(i+1)) + + # fronts + for i in range(n): + self.rectangularWall(h, h, "efef", callback=[self.frontCB], + move="left" if i<n-1 else "left up", label="front " + str(i+1)) + + if self.upright: + # bottom wall + self.rectangularWall(h, d, "ffef", move="up", label="bottom wall") + else: + # vertical wall + self.wall(d, d+s, h, move="up", label="vertical wall") + + # Colored windows + for i in range(n): + self.parts.disc(h-2*t, move="right", label="colored windows")
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/trayinsert.html b/html/_modules/boxes/generators/trayinsert.html new file mode 100644 index 0000000..ed2f64c --- /dev/null +++ b/html/_modules/boxes/generators/trayinsert.html @@ -0,0 +1,141 @@ + + + + + + + + boxes.generators.trayinsert — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.trayinsert

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
+
[docs]class TrayInsert(Boxes): + """Tray insert without floor and outer walls - allows only continuous walls""" + + ui_group = "Tray" + + def __init__(self): + Boxes.__init__(self) + self.buildArgParser("sx", "sy", "h", "outside") + + def render(self): + + if self.outside: + self.sx = self.adjustSize(self.sx, False, False) + self.sy = self.adjustSize(self.sy, False, False) + + x = sum(self.sx) + self.thickness * (len(self.sx) - 1) + y = sum(self.sy) + self.thickness * (len(self.sy) - 1) + h = self.h + t = self.thickness + + + # Inner walls + for i in range(len(self.sx) - 1): + e = [edges.SlottedEdge(self, self.sy, slots=0.5 * h), "e", "e", "e"] + self.rectangularWall(y, h, e, move="up") + + for i in range(len(self.sy) - 1): + e = ["e", "e", edges.SlottedEdge(self, self.sx[::-1], "e", slots=0.5 * h), "e"] + self.rectangularWall(x, h, e, move="up")
+ + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/traylayout.html b/html/_modules/boxes/generators/traylayout.html new file mode 100644 index 0000000..6561609 --- /dev/null +++ b/html/_modules/boxes/generators/traylayout.html @@ -0,0 +1,528 @@ + + + + + + + + boxes.generators.traylayout — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.traylayout

+#!/usr/bin/env python3
+# Copyright (C) 2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import sys, re
+from boxes import *
+import boxes
+import argparse
+
+
+
[docs]class TrayLayout(Boxes): + """Generate a typetray from a layout file""" + # This class generates the skeleton text file that can then be edited + # to describe the actual box + + description = """This is a two step process. The layout is based on a grid +of sizes in x and y direction. Choose how many distances you need in both directions. The actual sizes and all other settings can be entered in the second step.""" + + webinterface = True + + ui_group = "Tray" + + def __init__(self, input=None, webargs=False): + Boxes.__init__(self) + self.argparser = argparse.ArgumentParser() + self.argparser.add_argument( + "--x", action="store", type=int, default=2, + help="number of compartments side by side") + self.argparser.add_argument( + "--y", action="store", type=int, default=2, + help="number of compartments back to front") + self.argparser.add_argument( + "--output", action="store", type=str, default="traylayout.txt", + help="name of the layout text file") + + # Use empty open and close methods to avoid initializing the whole + # drawing infrastructure + + def open(self): + pass + + def close(self): + pass + + def fillDefault(self, x, y): + self.x = [0.0] * x + self.y = [0.0] * y + self.hwalls = [[True for i in range(x)] for j in range(y + 1)] + self.vwalls = [[True for i in range(x + 1)] for j in range(y)] + self.floors = [[True for i in range(x)] for j in range(y)] + + def __str__(self): + r = [] + + for i, x in enumerate(self.x): + r.append(" |" * (i) + " ,> %.1fmm\n" % x) + + for hwalls, vwalls, floors, y in zip( + self.hwalls, self.vwalls, self.floors, self.y): + r.append("".join(("+" + " -"[h] for h in hwalls)) + "+\n") + r.append("".join((" |"[v] + "X "[f] for v, f in zip(vwalls, floors))) + + " |"[vwalls[-1]] + " %.1fmm\n" % y) + r.append("".join(("+" + " -"[h] for h in self.hwalls[-1])) + "+\n") + + return "".join(r) + + def render(self): + self.fillDefault(self.x, self.y) + with open(self.output, 'w') as f: + f.write(str(self))
+ +
[docs]class TrayLayout2(TrayLayout): + """Generate a typetray from a layout file""" + + # This class reads in the layout either from a file (with --input) or + # as string (with --layout) and turns it into a drawing for a box. + + webinterface = True + + description = """Edit the layout text graphics to adjust your tray. +Put in the sizes for each column and row. You can replace the hyphens and +vertial bars representing the walls with a space character to remove the walls. +You can replace the space characters representing the floor by a "X" to remove the floor for this compartment. +""" + + def __init__(self, input=None, webargs=False): + Boxes.__init__(self) + self.addSettingsArgs(boxes.edges.FingerJointSettings) + self.buildArgParser("h", "hi", "outside") + if not webargs: + self.argparser.add_argument( + "--input", action="store", type=argparse.FileType('r'), + default='traylayout.txt', + help="layout file") + self.layout = None + else: + self.argparser.add_argument( + "--layout", action="store", type=str, default="") + + # Use normal open and close + open = Boxes.open + close = Boxes.close + + def vWalls(self, x, y): + "Number of vertical walls at a crossing" + result = 0 + if y > 0 and self.vwalls[y - 1][x]: + result += 1 + + if y < len(self.y) and self.vwalls[y][x]: + result += 1 + + return result + + def hWalls(self, x, y): + "Number of horizontal walls at a crossing" + result = 0 + if x > 0 and self.hwalls[y][x - 1]: + result += 1 + if x < len(self.x) and self.hwalls[y][x]: + result += 1 + return result + + def vFloor(self, x, y): + "Is there floor under vertical wall" + return ((x > 0 and self.floors[y][x - 1]) or + (x < len(self.x) and self.floors[y][x])) + + def hFloor(self, x, y): + "Is there foor under horizontal wall" + return ((y > 0 and self.floors[y - 1][x]) or + (y < len(self.y) and self.floors[y][x])) + + @restore + def edgeAt(self, edge, x, y, length, angle=0): + self.moveTo(x, y, angle) + edge = self.edges.get(edge, edge) + edge(length) + + def render(self): + + if self.layout: + self.parse(self.layout.split('\n')) + else: + self.parse(self.input) + + if self.outside: + self.x = self.adjustSize(self.x) + self.y = self.adjustSize(self.y) + self.h = self.adjustSize(self.h, e2=False) + + if self.hi: + self.hi = self.adjustSize(self.hi, e2=False) + + self.hi = hi = self.hi or self.h + + lx = len(self.x) + ly = len(self.y) + t = self.thickness + b = self.burn + t2 = self.thickness / 2.0 + + hasfloor = False + + for line in self.floors: + for f in line: + hasfloor |= f + + + self.edges["s"] = boxes.edges.Slot(self, self.hi / 2.0) + self.edges["C"] = boxes.edges.CrossingFingerHoleEdge(self, self.hi) + + self.ctx.save() + + # Horizontal Walls + for y in range(ly + 1): + if y == 0 or y == ly: + h = self.h + else: + h = self.hi + + start = 0 + end = 0 + + while end < lx: + lengths = [] + edges = [] + + while start < lx and not self.hwalls[y][start]: + start += 1 + + if start == lx: + break + + end = start + + while end < lx and self.hwalls[y][end]: + if self.hFloor(end, y): + edges.append("f") + else: + edges.append("e") # XXX E? + + lengths.append(self.x[end]) + edges.append("eCs"[self.vWalls(end + 1, y)]) + lengths.append(self.thickness) + end += 1 + + # remove last "slot" + lengths.pop() + edges.pop() + self.rectangularWall(sum(lengths), h, [ + boxes.edges.CompoundEdge(self, edges, lengths), + "f" if self.vWalls(end, y) else "e", + "e", + "f" if self.vWalls(start, y) else "e"], + move="right") + start = end + + self.ctx.restore() + self.rectangularWall(10, h, "ffef", move="up only") + self.ctx.save() + + # Vertical Walls + for x in range(lx + 1): + if x == 0 or x == lx: + h = self.h + else: + h = self.hi + start = 0 + end = 0 + + while end < ly: + lengths = [] + edges = [] + while start < ly and not self.vwalls[start][x]: + start += 1 + + if start == ly: + break + + end = start + + while end < ly and self.vwalls[end][x]: + if self.vFloor(x, end): + edges.append("f") + else: + edges.append("e") # XXX E? + + lengths.append(self.y[end]) + edges.append("eCs"[self.hWalls(x, end + 1)]) + lengths.append(self.thickness) + end += 1 + # remove last "slot" + lengths.pop() + edges.pop() + + upper = [{ + "f": "e", + "s": "s", + "e": "e", + "E": "e", + "C": "e"}[e] for e in reversed(edges)] + edges = ["e" if e == "s" else e for e in edges] + self.rectangularWall(sum(lengths), h, [ + boxes.edges.CompoundEdge(self, edges, lengths), + "eFf"[self.hWalls(x, end)], + boxes.edges.CompoundEdge(self, upper, list(reversed(lengths))), + "eFf"[self.hWalls(x, start)]], + move="right") + start = end + + self.ctx.restore() + self.rectangularWall(10, h, "ffef", move="up only") + self.moveTo(2 * self.thickness, 2 * self.thickness) + self.ctx.save() + + ########################################################## + ### Baseplate + ########################################################## + + # Horizontal lines + posy = 0 + for y in range(ly, -1, -1): + posx = self.thickness + for x in range(lx): + if self.hwalls[y][x]: + e = "F" + else: + e = "e" + if y < ly and self.floors[y][x]: + if y > 0 and self.floors[y - 1][x]: + # Inside Wall + if self.hwalls[y][x]: + self.fingerHolesAt(posx, posy + t2, self.x[x], angle=0) + else: + # Top edge + self.edgeAt(e, posx + self.x[x], posy + t + b, self.x[x], + -180) + if x == 0 or y == 0 or not self.floors[y - 1][x - 1]: + self.edgeAt("e", posx, posy + t + b, t, -180) + if x == lx - 1 or y == 0 or not self.floors[y - 1][x + 1]: + self.edgeAt("e", posx + self.x[x] + t, posy + t + b, t, -180) + elif y > 0 and self.floors[y - 1][x]: + # Bottom Edge + self.edgeAt(e, posx, posy - b, self.x[x]) + if x == 0 or y == ly or not self.floors[y][x - 1]: + self.edgeAt("e", posx - t, posy - b, t) + if x == lx - 1 or y == ly or not self.floors[y][x + 1]: + self.edgeAt("e", posx + self.x[x], posy - b, t) + posx += self.x[x] + self.thickness + posy += self.y[y - 1] + self.thickness + + posx = 0 + for x in range(lx + 1): + posy = self.thickness + for y in range(ly - 1, -1, -1): + if self.vwalls[y][x]: + e = "F" + else: + e = "e" + if x > 0 and self.floors[y][x - 1]: + if x < lx and self.floors[y][x]: + # Inside wall + if self.vwalls[y][x]: + self.fingerHolesAt(posx + t2, posy, self.y[y]) + else: + # Right edge + self.edgeAt(e, posx + t + b, posy, self.y[y], 90) + if x == lx or y == 0 or not self.floors[y - 1][x]: + self.edgeAt("e", posx + t + b, posy + self.y[y], t, 90) + if x == lx or y == ly - 1 or not self.floors[y + 1][x]: + self.edgeAt("e", posx + t + b, posy - t, t, 90) + elif x < lx and self.floors[y][x]: + # Left edge + self.edgeAt(e, posx - b, posy + self.y[y], self.y[y], -90) + if x == 0 or y == 0 or not self.floors[y - 1][x - 1]: + self.edgeAt("e", posx - b, posy + self.y[y] + t, t, -90) + if x == 0 or y == ly - 1 or not self.floors[y + 1][x - 1]: + self.edgeAt("e", posx -b, posy, t, -90) + posy += self.y[y] + self.thickness + if x < lx: + posx += self.x[x] + self.thickness + + + def parse(self, input): + x = [] + y = [] + hwalls = [] + vwalls = [] + floors = [] + for nr, line in enumerate(input): + if not line or line[0] == "#": + continue + m = re.match(r"( \|)* ,>\s*(\d*\.?\d+)\s*mm\s*", line) + if m: + x.append(float(m.group(2))) + continue + if line[0] == '+': + w = [] + for n, c in enumerate(line[:len(x)*2 + 1]): + if n % 2: + if c == ' ': + w.append(False) + elif c == '-': + w.append(True) + else: + pass + # raise ValueError(line) + else: + if c != '+': + pass + # raise ValueError(line) + + hwalls.append(w) + if line[0] in " |": + w = [] + f = [] + for n, c in enumerate(line[:len(x) * 2 + 1]): + if n % 2: + if c in 'xX': + f.append(False) + elif c == ' ': + f.append(True) + else: + raise ValueError('''Can't parse line %i in layout: expected " ", "x" or "X" for char #%i''' % (nr+1, n+1)) + else: + if c == ' ': + w.append(False) + elif c == '|': + w.append(True) + else: + raise ValueError('''Can't parse line %i in layout: expected " ", or "|" for char #%i''' % (nr+1, n+1)) + + floors.append(f) + vwalls.append(w) + m = re.match(r"([ |][ xX])+[ |]\s*(\d*\.?\d+)\s*mm\s*", line) + if not m: + raise ValueError('''Can't parse line %i in layout: Can read height of the row''' % (nr+1)) + else: + y.append(float(m.group(2))) + + # check sizes + lx = len(x) + ly = len(y) + + if lx == 0: + raise ValueError("Need more than one wall in x direction") + if ly == 0: + raise ValueError("Need more than one wall in y direction") + if len(hwalls) != ly + 1: + raise ValueError("Wrong number of horizontal wall lines: %i (%i expected)" % (len(hwalls), ly + 1)) + for nr, walls in enumerate(hwalls): + if len(walls) != lx: + raise ValueError("Wrong number of horizontal walls in line %i: %i (%i expected)" % (nr, len(walls), lx)) + if len(vwalls) != ly: + raise ValueError("Wrong number of vertical wall lines: %i (%i expected)" % (len(vwalls), ly)) + for nr, walls in enumerate(vwalls): + if len(walls) != lx + 1: + raise ValueError( + "Wrong number of vertical walls in line %i: %i (%i expected)" % (nr, len(walls), lx + 1)) + + self.x = x + self.y = y + self.hwalls = hwalls + self.vwalls = vwalls + self.floors = floors
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/trianglelamp.html b/html/_modules/boxes/generators/trianglelamp.html new file mode 100644 index 0000000..16f42db --- /dev/null +++ b/html/_modules/boxes/generators/trianglelamp.html @@ -0,0 +1,202 @@ + + + + + + + + boxes.generators.trianglelamp — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.trianglelamp

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+class CornerEdge(edges.Edge):
+    char = "C"
+    
+    def startwidth(self):
+        return self.boxes.thickness * math.tan(math.radians(90-22.5))
+
+    def __call__(self, length, bedBolts=None, bedBoltSettings=None, **kw):
+        with self.saved_context():
+            self.ctx.stroke()
+            self.set_source_color(Color.RED)
+            self.moveTo(0, self.startwidth())
+            self.edge(length)
+            self.ctx.stroke()
+            self.set_source_color(Color.BLACK)
+        super().__call__(length, bedBolts=None, bedBoltSettings=None, **kw)
+    
+
+
[docs]class TriangleLamp(Boxes): + """Triangle LED Lamp""" + + ui_group = "Misc" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings, finger=3.0,space=3.0, + surroundingspaces=0.5) + self.buildArgParser(x=250, h=40) + # Add non default cli params if needed (see argparse std lib) + self.argparser.add_argument( + "--cornersize", action="store", type=float, default=30, + help="short side of the corner triangles") + self.argparser.add_argument( + "--screenholesize", action="store", type=float, default=4, + help="diameter of the holes in the screen") + self.argparser.add_argument( + "--screwholesize", action="store", type=float, default=2, + help="diameter of the holes in the wood") + self.argparser.add_argument( + "--sharpcorners", action="store", type=boolarg, default=False, + help="extend walls for 45° corners. Requires grinding a 22.5° bevel.") + + def CB(self, l, size): + + def f(): + t = self.thickness + self.fingerHolesAt(0, self.h-1.5*t, size, 0) + self.fingerHolesAt(l, self.h-1.5*t, size, 180) + + return f + + def render(self): + # adjust to the variables you want in the local scope + x, h = self.x, self.h + l = (x**2+x**2)**.5 + c = self.cornersize + t = self.thickness + + r1 = self.screwholesize / 2 + r2 = self.screenholesize / 2 + + self.addPart(CornerEdge(self, None)) + + self.rectangularTriangle(x, x, num=2, move="up", callback=[ + lambda: self.hole(2/3.*c, 1/4.*c, r2), + lambda: (self.hole(1/3.*c, 1/3.*c, r2), + self.hole(x-2/3.*c, 1/4.*c, r2)), + ]) + self.rectangularTriangle(x, x, "fff", num=2, move="up") + + C = 'e' + if self.sharpcorners: + C = 'C' + + self.rectangularWall(x, h, "Ffe"+C, callback=[self.CB(x, c)], + move="up") + self.rectangularWall(x, h, "Ffe"+C, callback=[self.CB(x, c)], + move="up") + + self.rectangularWall(x, h, "F"+C+"eF", callback=[self.CB(x, c)], + move="up") + self.rectangularWall(x, h, "F"+C+"eF", callback=[self.CB(x, c)], + move="up") + + self.rectangularWall(l, h, "F"+C+"e" + C, + callback=[self.CB(l, c*2**.5)], move="up") + self.rectangularWall(l, h, "F"+C+"e" + C, + callback=[self.CB(l, c*2**.5)], move="up") + + + self.rectangularTriangle(c, c, "ffe", num=2, move="right", callback=[ + lambda:self.hole(2/3.*c,1/3.*c, r1)]) + self.rectangularTriangle(c, c, "fef", num=4, move="up", callback=[ + lambda: self.hole(2/3.*c, 1/4.*c, r1)])
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/two_piece.html b/html/_modules/boxes/generators/two_piece.html new file mode 100644 index 0000000..727b7d4 --- /dev/null +++ b/html/_modules/boxes/generators/two_piece.html @@ -0,0 +1,160 @@ + + + + + + + + boxes.generators.two_piece — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.two_piece

+#!/usr/bin/python3
+# Copyright (C) 2013-2018 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
+
[docs]class TwoPiece(Boxes): + """A two piece box where top slips over the bottom half to form + the enclosure. + """ + + description = """ +Set *hi* larger than *h* to leave gap between the inner and outer shell. This can be used to make opening the box easier. Set *hi* smaller to only have a small inner ridge that will allow the content to be momre visible after opening. + +![Bottom view](static/samples/TwoPiece2.jpg) +""" + + ui_group = "Box" + + def __init__(self): + Boxes.__init__(self) + self.buildArgParser("x", "y", "h", "hi", "outside") + self.addSettingsArgs(edges.FingerJointSettings, finger=2.0, space=2.0) + + self.argparser.add_argument( + "--play", action="store", type=float, default=0.15, + help="play between the two parts as multipleof the wall thickness") + + def render(self): + # adjust to the variables you want in the local scope + x, y, h = self.x, self.y, self.h + hi = self.hi or self.h + t = self.thickness + p = self.play * t + + if self.outside: + x -= 4*t + 2*p + y -= 4*t + 2*p + h -= 2 * t + hi -= 2 * t + + # Adjust h edge with play + self.edges["f"].settings.setValues(t, False, edge_width=self.edges["f"].settings.edge_width + p) + + for i in range(2): + d = i * 2 * (t+p) + height = [hi, h][i] + with self.saved_context(): + self.rectangularWall(x+d, height, "fFeF", move="right") + self.rectangularWall(y+d, height, "ffef", move="right") + self.rectangularWall(x+d, height, "fFeF", move="right") + self.rectangularWall(y+d, height, "ffef", move="right") + self.rectangularWall(y, height, "ffef", move="up only") + + self.rectangularWall(x, y, "hhhh", bedBolts=None, move="right") + self.rectangularWall(x+d, y+d, "FFFF", bedBolts=None, move="right")
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/typetray.html b/html/_modules/boxes/generators/typetray.html new file mode 100644 index 0000000..b32c0fe --- /dev/null +++ b/html/_modules/boxes/generators/typetray.html @@ -0,0 +1,288 @@ + + + + + + + + boxes.generators.typetray — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.typetray

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes.lids import _TopEdge
+
+
[docs]class TypeTray(_TopEdge): + """Type tray - allows only continuous walls""" + + ui_group = "Tray" + + def __init__(self): + Boxes.__init__(self) + self.addTopEdgeSettings(fingerjoint={"surroundingspaces": 0.5}, + roundedtriangle={"outset" : 1}) + self.buildArgParser("sx", "sy", "h", "hi", "outside", "bottom_edge", + "top_edge") + self.argparser.add_argument( + "--back_height", action="store", type=float, default=0.0, + help="additional height of the back wall - e top egde only") + self.argparser.add_argument( + "--radius", action="store", type=float, default=0.0, + help="radius for strengthening side walls with back_height") + self.argparser.add_argument( + "--gripheight", action="store", type=float, default=30, + dest="gh", help="height of the grip hole in mm") + self.argparser.add_argument( + "--gripwidth", action="store", type=float, default=70, + dest="gw", help="width of th grip hole in mm (zero for no hole)") + self.argparser.add_argument( + "--handle", type=boolarg, default=False, help="add handle to the bottom (changes bottom edge in the front)", + ) + + def xSlots(self): + posx = -0.5 * self.thickness + for x in self.sx[:-1]: + posx += x + self.thickness + posy = 0 + for y in self.sy: + self.fingerHolesAt(posx, posy, y) + posy += y + self.thickness + + def ySlots(self): + posy = -0.5 * self.thickness + for y in self.sy[:-1]: + posy += y + self.thickness + posx = 0 + for x in reversed(self.sx): + self.fingerHolesAt(posy, posx, x) + posx += x + self.thickness + + def xHoles(self): + posx = -0.5 * self.thickness + for x in self.sx[:-1]: + posx += x + self.thickness + self.fingerHolesAt(posx, 0, self.hi) + + def yHoles(self): + posy = -0.5 * self.thickness + for y in self.sy[:-1]: + posy += y + self.thickness + self.fingerHolesAt(posy, 0, self.hi) + + def gripHole(self): + if not self.gw: + return + + x = sum(self.sx) + self.thickness * (len(self.sx) - 1) + r = min(self.gw, self.gh) / 2.0 + self.rectangularHole(x / 2.0, self.gh * 1.5, self.gw, self.gh, r) + + def render(self): + if self.outside: + self.sx = self.adjustSize(self.sx) + self.sy = self.adjustSize(self.sy) + self.h = self.adjustSize(self.h, e2=False) + if self.hi: + self.hi = self.adjustSize(self.hi, e2=False) + + x = sum(self.sx) + self.thickness * (len(self.sx) - 1) + y = sum(self.sy) + self.thickness * (len(self.sy) - 1) + h = self.h + sameh = not self.hi + hi = self.hi = self.hi or h + t = self.thickness + + # outer walls + b = self.bottom_edge + tl, tb, tr, tf = self.topEdges(self.top_edge) + self.closedtop = self.top_edge in "fFhŠ" + + bh = self.back_height if self.top_edge == "e" else 0.0 + + # x sides + + self.ctx.save() + + # outer walls - front/back + if bh: + self.rectangularWall(x, h+bh, [b, "f", tb, "f"], + callback=[self.xHoles], + ignore_widths=[], + move="up", label="back") + self.rectangularWall(x, h, ["f" if self.handle else b, "f", tf, "f"], + callback=[self.mirrorX(self.xHoles, x), + None, self.gripHole], + move="up", label="front") + else: + self.rectangularWall(x, h, [b, "F", tb, "F"], + callback=[self.xHoles], + ignore_widths=[1, 6], + move="up", label="back") + self.rectangularWall(x, h, ["f" if self.handle else b, "F", tf, "F"], + callback=[self.mirrorX(self.xHoles, x), + None, self.gripHole], + ignore_widths=[] if self.handle else [1, 6], + move="up", label="front") + + # floor + if b != "e": + if self.handle: + self.rectangularWall(x, y, "ffYf", callback=[self.xSlots, self.ySlots], move="up", label="bottom") + else: + self.rectangularWall(x, y, "ffff", callback=[self.xSlots, self.ySlots], move="up", label="bottom") + + # Inner walls + + be = "f" if b != "e" else "e" + + for i in range(len(self.sy) - 1): + e = [edges.SlottedEdge(self, self.sx, be), "f", + edges.SlottedEdge(self, self.sx[::-1], "e", slots=0.5 * hi), "f"] + if self.closedtop and sameh: + e = [edges.SlottedEdge(self, self.sx, be), "f", + edges.SlottedEdge(self, self.sx[::-1], "f", slots=0.5 * hi), "f"] + + self.rectangularWall(x, hi, e, move="up", label=f"inner x {i+1}") + + # top / lid + if self.closedtop and sameh: + e = "FFFF" if self.top_edge == "f" else "ffff" + self.rectangularWall(x, y, e, callback=[ + self.xSlots, self.ySlots], move="up", label="top") + else: + self.drawLid(x, y, self.top_edge) + + self.ctx.restore() + self.rectangularWall(x, hi, "ffff", move="right only") + + # y walls + + # outer walls - left/right + + if bh: + self.trapezoidSideWall( + y, h, h+bh, [b, "h", "e", "h"], + radius=self.radius, callback=[self.yHoles, ], + move="up", label="left side") + self.trapezoidSideWall( + y, h+bh, h, [b, "h", "e", "h"], radius=self.radius, + callback=[self.mirrorX(self.yHoles, y), ], + move="up", label="right side") + else: + self.rectangularWall( + y, h, [b, "f", tl, "f"], callback=[self.yHoles, ], + ignore_widths=[6] if self.handle else [1, 6], + move="up", label="left side") + self.rectangularWall( + y, h, [b, "f", tr, "f"], + callback=[self.mirrorX(self.yHoles, y), ], + ignore_widths=[1] if self.handle else [1, 6], + move="up", label="right side") + + # inner walls + for i in range(len(self.sx) - 1): + e = [edges.SlottedEdge(self, self.sy, be, slots=0.5 * hi), + "f", "e", "f"] + if self.closedtop and sameh: + e = [edges.SlottedEdge(self, self.sy, be, slots=0.5 * hi),"f", + edges.SlottedEdge(self, self.sy[::-1], "f"), "f"] + self.rectangularWall(y, hi, e, move="up", label=f"inner y {i+1}")
+ + + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/ubox.html b/html/_modules/boxes/generators/ubox.html new file mode 100644 index 0000000..7a88310 --- /dev/null +++ b/html/_modules/boxes/generators/ubox.html @@ -0,0 +1,197 @@ + + + + + + + + boxes.generators.ubox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.ubox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2017 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes.lids import _TopEdge, _ChestLid
+import math
+
+
[docs]class UBox(_TopEdge, _ChestLid): + """Box various options for different stypes and lids""" + + ui_group = "FlexBox" + + def __init__(self): + Boxes.__init__(self) + self.addTopEdgeSettings() + self.addSettingsArgs(edges.FlexSettings) + self.buildArgParser("top_edge", "x", "y", "h") + self.argparser.add_argument( + "--radius", action="store", type=float, default=30.0, + help="radius of bottom corners") + self.argparser.add_argument( + "--lid", action="store", type=str, default="default (none)", + choices=("default (none)", "chest", "flat"), + help="additional lid") + self.angle = 0 + + def U(self, x, y, r, edge="e", move=None, label=""): + + e = self.edges.get(edge, edge) + + w = self.edges["f"].spacing() + tw = x+2*w + th = y+w+e.spacing() + if self.move(tw, th, move, True, label=label): + return + + self.moveTo(w+r, w) + self.edges["f"](x-2*r) + self.corner(90, r) + self.edges["f"](y-r) + self.edgeCorner("f", e) + e(x) + self.edgeCorner(e, "f") + self.edges["f"](y-r) + self.corner(90, r) + + self.move(tw, th, move, label=label) + + def Uwall(self, x, y, h, r, edges="ee", move=None, label=""): + + e = [self.edges.get(edge, edge) for edge in edges] + + w = self.edges["F"].spacing() + cl = r*math.pi/2 + + tw = 2*y + x - 4*(cl-r) + e[0].spacing() + e[1].spacing() + th = h + 2*w + if self.move(tw, th, move, True, label=label): + return + + self.moveTo(e[0].spacing()) + + for nr, flex in enumerate("XE"): + self.edges["F"](y-r) + if x-2*r > 0.1 * self.thickness: + self.edges[flex](cl, h=th) + self.edges["F"](x-2*r) + self.edges[flex](cl, h=th) + else: + self.edges[flex](2*cl+x-2*r, h=th) + self.edges["F"](y-r) + self.edgeCorner("F", e[nr]) + e[nr](h) + self.edgeCorner(e[nr], "F") + + self.move(tw, th, move, label=label) + + def render(self): + x, y, h, r = self.x, self.y, self.h, self.radius + + self.radius = r = min(r, x/2.0, y) + + + t1, t2, t3, t4 = self.topEdges(self.top_edge) + + self.U(x, y, r, t1, move="right", label="left") + self.U(x, y, r, t3, move="up", label="right") + self.U(x, y, r, t3, move="left only", label="invisible") + self.Uwall(x, y, h, r, [t2, t4], move="up", label="wall") + + self.drawLid(x, h, self.top_edge) + self.drawAddOnLid(x, h, self.lid)
+ + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/unevenheightbox.html b/html/_modules/boxes/generators/unevenheightbox.html new file mode 100644 index 0000000..fbc8740 --- /dev/null +++ b/html/_modules/boxes/generators/unevenheightbox.html @@ -0,0 +1,194 @@ + + + + + + + + boxes.generators.unevenheightbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.unevenheightbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2018 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
+
[docs]class UnevenHeightBox(Boxes): + """Box with different height in each corner""" + + ui_group = "Box" + + def __init__(self): + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.addSettingsArgs(edges.GroovedSettings) + self.buildArgParser("x", "y", "outside", bottom_edge="F") + self.argparser.add_argument( + "--height0", action="store", type=float, default=50, + help="height of the front left corner in mm") + self.argparser.add_argument( + "--height1", action="store", type=float, default=50, + help="height of the front right corner in mm") + self.argparser.add_argument( + "--height2", action="store", type=float, default=100, + help="height of the right back corner in mm") + self.argparser.add_argument( + "--height3", action="store", type=float, default=100, + help="height of the left back corner in mm") + self.argparser.add_argument( + "--lid", action="store", type=boolarg, default=False, + help="add a lid (works best with high corners opposing each other)") + self.argparser.add_argument( + "--lid_height", action="store", type=float, default=0, + help="additional height of the lid") + self.argparser.add_argument( + "--edge_types", action="store", type=str, default="eeee", + help="which edges are flat (e) or grooved (z,Z), counter-clockwise from the front") + + def render(self): + + x, y = self.x, self.y + heights = [self.height0, self.height1, self.height2, self.height3] + + edge_types = self.edge_types + if len(edge_types) != 4 or any(et not in "ezZ" for et in edge_types): + raise ValueError("Wrong edge_types style: %s)" % edge_types) + + if self.outside: + x = self.adjustSize(x) + y = self.adjustSize(y) + for i in range(4): + heights[i] = self.adjustSize(heights[i], self.bottom_edge, + self.lid) + + t = self.thickness + h0, h1, h2, h3 = heights + b = self.bottom_edge + + self.trapezoidWall(x, h0, h1, [b, "F", edge_types[0], "F"], move="right") + self.trapezoidWall(y, h1, h2, [b, "f", edge_types[1], "f"], move="right") + self.trapezoidWall(x, h2, h3, [b, "F", edge_types[2], "F"], move="right") + self.trapezoidWall(y, h3, h0, [b, "f", edge_types[3], "f"], move="right") + + with self.saved_context(): + if b != "e": + self.rectangularWall(x, y, "ffff", move="up") + + if self.lid: + maxh = max(heights) + lidheights = [maxh-h+self.lid_height for h in heights] + h0, h1, h2, h3 = lidheights + lidheights += lidheights + edges = ["E" if (lidheights[i] == 0.0 and lidheights[i+1] == 0.0) else "f" for i in range(4)] + self.rectangularWall(x, y, edges, move="up") + + if self.lid: + self.moveTo(0, maxh+self.lid_height+self.edges["F"].spacing()+self.edges[b].spacing()+1*self.spacing, 180) + edge_inverse = {"e": "e", "z": "Z", "Z": "z"} + edge_types = [edge_inverse[et] for et in edge_types] + + self.trapezoidWall(y, h0, h3, "Ff" + edge_types[3] + "f", move="right" + + (" only" if h0 == h3 == 0.0 else "")) + self.trapezoidWall(x, h3, h2, "FF" + edge_types[2] + "F", move="right" + + (" only" if h3 == h2 == 0.0 else "")) + self.trapezoidWall(y, h2, h1, "Ff" + edge_types[1] + "f", move="right" + + (" only" if h2 == h1 == 0.0 else "")) + self.trapezoidWall(x, h1, h0, "FF" + edge_types[0] + "F", move="right" + + (" only" if h1 == h0 == 0.0 else ""))
+ + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/universalbox.html b/html/_modules/boxes/generators/universalbox.html new file mode 100644 index 0000000..45848af --- /dev/null +++ b/html/_modules/boxes/generators/universalbox.html @@ -0,0 +1,198 @@ + + + + + + + + boxes.generators.universalbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.universalbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes.edges import Bolts
+from boxes.lids import _TopEdge, _ChestLid
+
+
[docs]class UniversalBox(_TopEdge, _ChestLid): + """Box with various options for different styles and lids""" + + ui_group = "Box" + + def __init__(self): + Boxes.__init__(self) + self.addTopEdgeSettings(roundedtriangle={"outset" : 1}, + hinge={"outset" : True}) + self.addSettingsArgs(edges.FlexSettings) + self.buildArgParser("top_edge", "bottom_edge", + "x", "y", "h", "outside") + self.argparser.add_argument( + "--vertical_edges", action="store", type=str, + default="finger joints", + choices=("finger joints", "finger holes"), + help="connections used for the vertical edges") + self.argparser.add_argument( + "--lid", action="store", type=str, default="default (none)", + choices=("default (none)", "chest", "flat"), + help="additional lid (for straight top_edge only)") + + def top_hole(self, x, y, top_edge): + t = self.thickness + + if top_edge == "f": + edge = self.edges["F"] + self.moveTo(2*t+self.burn, 2*t, 90) + elif top_edge == "F": + edge = self.edges["f"] + self.moveTo(t+self.burn, 2*t, 90) + else: + raise ValueError("Only f and F supported") + + for l in (y, x, y, x): + edge(l) + if top_edge == "F": self.edge(t) + self.corner(-90) + if top_edge == "F": self.edge(t) + + def render(self): + x, y, h = self.x, self.y, self.h + t = self.thickness + + t1, t2, t3, t4 = self.topEdges(self.top_edge) + b = self.edges.get(self.bottom_edge, self.edges["F"]) + + d2 = Bolts(2) + d3 = Bolts(3) + + d2 = d3 = None + + sideedge = "F" if self.vertical_edges == "finger joints" else "h" + + if self.outside: + self.x = x = self.adjustSize(x, sideedge, sideedge) + self.y = y = self.adjustSize(y) + self.h = h = self.adjustSize(h, b, self.top_edge) + + with self.saved_context(): + self.rectangularWall(x, h, [b, sideedge, t1, sideedge], + ignore_widths=[1, 6], + bedBolts=[d2], move="up", label="left") + self.rectangularWall(x, h, [b, sideedge, t3, sideedge], + ignore_widths=[1, 6], + bedBolts=[d2], move="up", label="right") + + if self.bottom_edge != "e": + self.rectangularWall(x, y, "ffff", bedBolts=[d2, d3, d2, d3], move="up", label="bottom") + if self.top_edge in "fF": + self.set_source_color(Color.MAGENTA) # I don't know why this part has a different color, but RED is not a good choice because RED is used for annotations + self.rectangularWall(x+4*t, y+4*t, callback=[ + lambda:self.top_hole(x, y, self.top_edge)], move="up", label="top hole") + self.set_source_color(Color.BLACK) + self.drawLid(x, y, self.top_edge, [d2, d3]) + self.drawAddOnLid(x, y, self.lid) + + self.rectangularWall(x, h, [b, sideedge, t3, sideedge], + ignore_widths=[1, 6], + bedBolts=[d2], move="right only", label="invisible") + self.rectangularWall(y, h, [b, "f", t2, "f"], + ignore_widths=[1, 6], + bedBolts=[d3], move="up", label="back") + self.rectangularWall(y, h, [b, "f", t4, "f"], + ignore_widths=[1, 6], + bedBolts=[d3], move="up", label="front")
+ + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/waivyknob.html b/html/_modules/boxes/generators/waivyknob.html new file mode 100644 index 0000000..0e4cb8d --- /dev/null +++ b/html/_modules/boxes/generators/waivyknob.html @@ -0,0 +1,149 @@ + + + + + + + + boxes.generators.waivyknob — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.waivyknob

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class WaivyKnob(Boxes): + """Round knob serrated outside for better gripping""" + + ui_group = "Part" + + def __init__(self): + Boxes.__init__(self) + + # Add non default cli params if needed (see argparse std lib) + self.argparser.add_argument( + "--diameter", action="store", type=float, default=50., + help="Diameter of the knob (mm)") + self.argparser.add_argument( + "--serrations", action="store", type=int, default=20, + help="Number of serrations") + self.argparser.add_argument( + "--serrationangle", action="store", type=float, default=45., + help="higher values for deeper serrations (degrees)") + self.argparser.add_argument( + "--bolthole", action="store", type=float, default=6., + help="Diameter of the bolt hole (mm)") + self.argparser.add_argument( + "--dhole", action="store", type=float, default=1., + help="D-Flat in fraction of the diameter") + self.argparser.add_argument( + "--hexhead", action="store", type=float, default=10., + help="Width of the hex bolt head (mm)") + + def render(self): + t = self.thickness + angle = self.serrationangle + self.parts.waivyKnob(self.diameter, self.serrations, angle, + callback=lambda:self.dHole(0, 0, d=self.bolthole, + rel_w=self.dhole), + move="right") + self.parts.waivyKnob(self.diameter, self.serrations, angle, + callback=lambda: self.nutHole(self.hexhead), + move="right") + self.parts.waivyKnob(self.diameter, self.serrations, angle)
+ + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/wallcaliperholder.html b/html/_modules/boxes/generators/wallcaliperholder.html new file mode 100644 index 0000000..ce178c6 --- /dev/null +++ b/html/_modules/boxes/generators/wallcaliperholder.html @@ -0,0 +1,155 @@ + + + + + + + + boxes.generators.wallcaliperholder — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.wallcaliperholder

+#!/usr/bin/env python3
+# Copyright (C) 2013-2019 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes.walledges import _WallMountedBox
+
+
[docs]class WallCaliper(_WallMountedBox): + """Holds a single caliper to a wall""" + + def __init__(self): + super().__init__() + + # remove cli params you do not need + self.buildArgParser(h=100) + # Add non default cli params if needed (see argparse std lib) + self.argparser.add_argument( + "--width", action="store", type=float, default=18.0, + help="width of the long end") + self.argparser.add_argument( + "--heigth", action="store", type=float, default=6.0, + help="heigth of the body") + + def side(self, move=None): + t = self.thickness + h = self.h + hc = self.heigth + + tw = self.edges["b"].spacing() + hc + 8*t + + if self.move(tw, h, move, True): + return + + self.moveTo(self.edges["b"].startwidth()) + self.polyline(5*t+hc, (90, 2*t), h/2-2*t, (180, 1.5*t), 0.25*h, + -90, hc, -90, 0.75*h-2*t, (90, 2*t), 2*t, 90) + + self.edges["b"](h) + + self.move(tw, h, move) + + def render(self): + self.generateWallEdges() + + t = self.thickness + h = self.h + + self.side(move="right") + self.side(move="right") + w = self.width + self.flangedWall(w, h, flanges=[0, 2*t, 0, 2*t], edges="eeee", + r=2*t, + callback=[lambda:(self.wallHolesAt(1.5*t, 0, h, 90), self.wallHolesAt(w+2.5*t, 0, h, 90))])
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/wallchiselholder.html b/html/_modules/boxes/generators/wallchiselholder.html new file mode 100644 index 0000000..a1536f2 --- /dev/null +++ b/html/_modules/boxes/generators/wallchiselholder.html @@ -0,0 +1,215 @@ + + + + + + + + boxes.generators.wallchiselholder — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.wallchiselholder

+#!/usr/bin/env python3
+# Copyright (C) 2013-2019 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes.walledges import _WallMountedBox
+
+class FrontEdge(edges.Edge):
+
+    def __call__(self, length, **kw):
+        td = self.tooldiameter
+        rh = self.holediameter / 2.0
+        r = self.radius
+        sw = self.slot_width
+
+        a = math.degrees(math.asin((r+sw/2)/(r+rh)))
+        l = (td - sw - 2*r) / 2
+
+        for i in range(self.number):
+            self.polyline(l, (180-a, r), 0, (-360+2*a, rh), 0, (180-a, r), l)
+            
+        
+
+
[docs]class WallChiselHolder(_WallMountedBox): + """Wall tool holder for chisels, files and similar tools""" + + def __init__(self): + super().__init__() + + self.buildArgParser(h=120) + + self.argparser.add_argument( + "--tooldiameter", action="store", type=float, default=30., + help="diameter of the tool including space to grab") + self.argparser.add_argument( + "--holediameter", action="store", type=float, default=30., + help="diameter of the hole for the tool (handle should not fit through)") + self.argparser.add_argument( + "--slot_width", action="store", type=float, default=5., + help="width of slots") + #self.argparser.add_argument( + # "--angle", action="store", type=float, default=0., + # help="angle of the top - positive for leaning backwards") + self.argparser.add_argument( + "--radius", action="store", type=float, default=5., + help="radius at the slots") + self.argparser.add_argument( + "--number", action="store", type=int, default=6, + help="number of tools/slots") + self.argparser.add_argument( + "--hooks", action="store", type=str, default="all", + choices=("all", "odds", "everythird"), + help="amount of hooks / braces") + + def brace(self, i): + n = self.number + if i in (0, n): + return True + # fold for symmetry + #if i > n//2: + # i = n - i + if self.hooks == "all": + return True + elif self.hooks == "odds": + return not (i % 2) + elif self.hooks == "everythird": + return not (i % 3) + + def braces(self): + return sum((self.brace(i) for i in range(self.number+1))) + + def backCB(self): + n = self.number + rt = self.holediameter + wt = self.tooldiameter + t = self.thickness + + d = min(2*t, (wt-rt)/4.) + self.wallHolesAt(d, 0, self.h, 90) + self.wallHolesAt(n*wt-d, 0, self.h, 90) + + for i in range(1, n): + if self.brace(i): + self.wallHolesAt(i*wt, 0, self.h, 90) + + def topCB(self): + n = self.number + rt = self.holediameter + wt = self.tooldiameter + t = self.thickness + l = self.depth + + d = min(2*t, (wt-rt)/4.) + self.fingerHolesAt(d, 0, l, 90) + self.fingerHolesAt(n*wt-d, 0, l, 90) + + for i in range(1, n): + if self.brace(i): + self.fingerHolesAt(i*wt, 0, l, 90) + + def render(self): + self.generateWallEdges() + + t = self.thickness + wt = self.tooldiameter + n = self.number + + self.depth = depth = wt + 4*t + + self.rectangularWall(n*wt, self.h, "eeee", callback=[self.backCB], move="up") + self.rectangularWall(n*wt, depth, [FrontEdge(self, None), "e","e","e"], callback=[self.topCB], move="up") + self.moveTo(0, t) + self.rectangularTriangle(depth, self.h, "fbe", r=3*t, num=self.braces())
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/wallconsole.html b/html/_modules/boxes/generators/wallconsole.html new file mode 100644 index 0000000..0c23d6e --- /dev/null +++ b/html/_modules/boxes/generators/wallconsole.html @@ -0,0 +1,158 @@ + + + + + + + + boxes.generators.wallconsole — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.wallconsole

+#!/usr/bin/env python3
+# Copyright (C) 2013-2019 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes.walledges import _WallMountedBox
+
+
[docs]class WallConsole(_WallMountedBox): + """Outset and angled plate to mount stuff to""" + + def __init__(self): + super().__init__() + + self.buildArgParser(sx=100, h=100, outside=True) + + self.argparser.add_argument( + "--top_depth", action="store", type=float, default=50, + help="depth at the top") + self.argparser.add_argument( + "--bottom_depth", action="store", type=float, default=35, + help="depth at the bottom") + + def backHoles(self): + posx = -0.5 * self.thickness + for x in self.sx[:-1]: + posx += x + self.thickness + self.wallHolesAt(posx, 0, self.h, 90) + + def frontHoles(self): + posx = -0.5 * self.thickness + for x in self.sx[:-1]: + posx += x + self.thickness + self.fingerHolesAt(posx, 0, self.front, 90) + + def render(self): + + self.generateWallEdges() + + if self.outside: + self.sx = self.adjustSize(self.sx) + self.h = self.adjustSize(self.h) + + x = sum(self.sx) + self.thickness * (len(self.sx) - 1) + h = self.h + td = self.top_depth + bd = self.bottom_depth + + self.front = (h**2 + (td-bd)**2)**0.5 + + self.rectangularWall(x, h, "eCec", callback=[self.backHoles], + move="up") + self.rectangularWall(x, self.front, "eFeF", + callback=[self.frontHoles], move="up") + + for i in range(len(self.sx)+1): + self.trapezoidWall(h, td, bd, "befe", move="up")
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/walldrillbox.html b/html/_modules/boxes/generators/walldrillbox.html new file mode 100644 index 0000000..82db682 --- /dev/null +++ b/html/_modules/boxes/generators/walldrillbox.html @@ -0,0 +1,144 @@ + + + + + + + + boxes.generators.walldrillbox — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.walldrillbox

+#!/usr/bin/env python3
+# Copyright (C) 2013-2019 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from .drillstand import DrillStand
+from boxes.walledges import _WallMountedBox
+
+
[docs]class WallDrillBox(DrillStand, _WallMountedBox): + """Box for drills with each compartment with a different height""" + ui_group = "WallMounted" + + def __init__(self): + _WallMountedBox.__init__(self) # don't call DrillStand.__init__ + + self.addSettingsArgs(edges.StackableSettings, height=1.0, width=3) + self.buildArgParser(sx="25*6", sy="10:20:30", sh="25:40:60") + self.argparser.add_argument( + "--extra_height", action="store", type=float, default=15.0, + help="height difference left to right") + + def render(self): + self.generateWallEdges() + + t = self.thickness + sx, sy, sh = self.sx, self.sy, self.sh + self.x = x = sum(sx) + len(sx)*t - t + self.y = y = sum(sy) + len(sy)*t - t + + bottom_angle = math.atan(self.extra_height / x) # radians + + self.xOutsideWall(sh[0], "hFeF", move="up") + for i in range(1, len(sy)): + self.xWall(i, move="up") + self.xOutsideWall(sh[-1], "hCec", move="up") + + self.rectangularWall(x/math.cos(bottom_angle)-t*math.tan(bottom_angle), y, "fefe", callback=[self.bottomCB], move="up") + + self.sideWall(edges="eBf", foot_height=2*t, move="right") + for i in range(1, len(sx)): + self.yWall(i, move="right") + self.sideWall(self.extra_height, foot_height=2*t, edges="eBf", move="right")
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/walledges.html b/html/_modules/boxes/generators/walledges.html new file mode 100644 index 0000000..e7d8410 --- /dev/null +++ b/html/_modules/boxes/generators/walledges.html @@ -0,0 +1,140 @@ + + + + + + + + boxes.generators.walledges — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.walledges

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes.walledges import _WallMountedBox
+
+
[docs]class WallEdges(_WallMountedBox): + """Shows the different edge types for wall systems""" + + def __init__(self): + super().__init__() + self.buildArgParser(h=120) + + def render(self): + self.generateWallEdges() + + h = self.h + + self.moveTo(0, 25) + self.rectangularWall( + 40, h, "eAea", move="right", + callback=[lambda : (self.text("a", 0, -20), + self.text("A", 30, -20))]) + self.rectangularWall( + 40, h, "eBeb", move="right", + callback=[lambda : (self.text("b", 0, -20), + self.text("B", 30, -20))]) + self.rectangularWall(40, h, "eCec", + callback=[lambda : (self.text("c", 0, -20), + self.text("C", 30, -20), + self.text("wallHolesAt", -5, -30), + self.wallHolesAt(20, 0, h, 90))], move="right") + self.moveTo(10) + self.rectangularWall( + 40, h, "eDed", move="right", + callback=[lambda : (self.text("d", 0, -20), + self.text("D", 30, -20))])
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/wallpinrow.html b/html/_modules/boxes/generators/wallpinrow.html new file mode 100644 index 0000000..712d816 --- /dev/null +++ b/html/_modules/boxes/generators/wallpinrow.html @@ -0,0 +1,267 @@ + + + + + + + + boxes.generators.wallpinrow — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.wallpinrow

+#!/usr/bin/env python3
+# Copyright (C) 2013-2019 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes.walledges import _WallMountedBox
+
+class PinEdge(edges.BaseEdge):
+    def __call__(self, length, **kw):
+        w2 = self.settings.pinwidth/2
+        l = self.settings.pinlength
+        s = self.settings.pinspacing
+        inc = self.settings.pinspacing_increment
+        t = self.settings.thickness
+        
+        pin = [0, -90, l+t-w2, (180, w2), l+t-w2, -90]
+
+        self.edge(s/2-w2)
+        s += inc/2
+        for i in range(self.pins-1):
+            self.polyline(*pin, s-2*w2)
+            s+=inc
+        self.polyline(*pin, s/2-w2-inc/4)
+
+    def margin(self):
+        return self.settings.thickness+self.settings.pinlength
+
+
[docs]class WallPinRow(_WallMountedBox): + """Outset and angled plate to mount stuff to""" + + def __init__(self): + super().__init__() + + self.argparser.add_argument( + "--pins", action="store", type=int, default=8, + help="number of pins") + self.argparser.add_argument( + "--pinlength", action="store", type=float, default=35, + help="length of pins (in mm)") + self.argparser.add_argument( + "--pinwidth", action="store", type=float, default=10, + help="width of pins (in mm)") + self.argparser.add_argument( + "--pinspacing", action="store", type=float, default=35, + help="space from middle to middle of pins (in mm)") + self.argparser.add_argument( + "--pinspacing_increment", action="store", type=float, default=0.0, + help="increase spacing from left to right (in mm)") + self.argparser.add_argument( + "--angle", action="store", type=float, default=20.0, + help="angle of the pins pointing up (in degrees)") + + self.argparser.add_argument( + "--hooks", action="store", type=int, default=3, + help="number of hooks into the wall") + self.argparser.add_argument( + "--h", action="store", type=float, default=50.0, + help="height of the front plate (in mm) - needs to be at least 7 time the thickness") + + def frontCB(self): + s = self.pinspacing + inc = self.pinspacing_increment + t = self.thickness + + pos = s/2 + s += 0.5*inc + for i in range(self.pins): + self.rectangularHole(pos, 2*t, self.pinwidth, t) + pos += s + s+=inc + + for i in range(1, self.hooks-1): + self.fingerHolesAt(i*self.x/(self.hooks-1), self.h/2, self.h/2) + + + def backCB(self): + t = self.thickness + self.fingerHolesAt(0, 2*t, self.x, 0) + if self.angle < 0.001: + return + for i in range(1, self.hooks-1): + self.fingerHolesAt(i*self.x/(self.hooks-1), 3*t, self.h/2-3*t) + + def sideWall(self, move=None): + a = self.angle + ar = math.radians(a) + h = self.h + t = self.thickness + + sh = math.sin(ar)*6*t + math.cos(ar)*h + + tw = self.edges["a"].margin() + math.sin(ar)*h + math.cos(ar)*6*t + th = sh + 6 + if self.move(tw, th, move, True): + return + + self.moveTo(self.edges["a"].margin()) + + self.polyline(math.sin(ar)*h, a, 4*t) + self.fingerHolesAt(-3.5*t, 0, h/2, 90) + self.edgeCorner("e", "h") + self.edges["h"](h) + self.polyline(0, 90-a, math.cos(ar)*6*t, 90) + self.edges["a"](sh) + self.corner(90) + + self.move(tw, th, move) + + + def supportWall(self, move=None): + a = self.angle + ar = math.radians(a) + h = self.h + t = self.thickness + + sh = math.sin(ar)*6*t + math.cos(ar)*h + + tw = self.edges["a"].margin() + max( + math.sin(ar)*h/2 + math.cos(ar)*5*t, + math.sin(ar)*h) + th = sh + 6 + if self.move(tw, th, move, True): + return + + self.moveTo(self.edges["a"].margin()) + + if a > 0.001: + self.polyline(math.sin(ar)*h, a+90, 3*t) + self.edges["f"](h/2-3*t) + self.polyline(0, -90) + self.polyline(4*t, 90) + self.edges["f"](h/2) + self.polyline(math.sin(ar)*2*t, 90-a, + math.cos(ar)*4*t - math.sin(ar)**2*2*t, 90) + if a > 0.001: + self.edges["a"](sh) + else: + self.edges["a"](h/2) + self.corner(90) + + self.move(tw, th, move) + + + def render(self): + self.generateWallEdges() + + p = PinEdge(self, self) + n = self.pins + t = self.thickness + + if self.h < 7*t: + self.h = 7*t + + self.x = x = n*self.pinspacing + (n)*(n-1)/2 *self.pinspacing_increment + + + self.rectangularWall(x, 3*t, [p, "e", "f", "e"], move="up") + self.rectangularWall(x, self.h, "efef", callback=[self.frontCB], + move="up") + self.rectangularWall(x, self.h/2, "efef", callback=[self.backCB], + move="up") + self.sideWall(move="right") + for i in range(self.hooks-2): + self.supportWall(move="right") + self.sideWall(move="right")
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/wallplaneholder.html b/html/_modules/boxes/generators/wallplaneholder.html new file mode 100644 index 0000000..aedbf53 --- /dev/null +++ b/html/_modules/boxes/generators/wallplaneholder.html @@ -0,0 +1,150 @@ + + + + + + + + boxes.generators.wallplaneholder — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.wallplaneholder

+#!/usr/bin/env python3
+# Copyright (C) 2013-2019 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes.walledges import _WallMountedBox
+
+
[docs]class WallPlaneHolder(_WallMountedBox): + """Hold a plane to a wall""" + + def __init__(self): + super().__init__() + + self.argparser.add_argument( + "--width", action="store", type=float, default=80, + help="width of the plane") + self.argparser.add_argument( + "--length", action="store", type=float, default=250, + help="legth of the plane") + self.argparser.add_argument( + "--hold_length", action="store", type=float, default=30, + help="legth of the part hiolding the plane over the front") + self.argparser.add_argument( + "--height", action="store", type=float, default=80, + help="height of the front of plane") + + def side(self): + l, w, h = self.length, self.width, self.height + hl = self.hold_length + t = self.thickness + self.fingerHolesAt(1.5*t, 2*t, 0.25*l, 90) + self.fingerHolesAt(1.5*t, 2*t+0.75*l, 0.25*l, 90) + self.fingerHolesAt(2.5*t+h, 2*t+l-hl, hl, 90) + self.fingerHolesAt(2*t, 1.5*t, h+2*t, 0) + + def render(self): + self.generateWallEdges() + + l, w, h = self.length, self.width, self.height + t = self.thickness + self.rectangularWall(h+4*t, l+2*t, "eeea", callback=[self.side], + move="right") + self.rectangularWall(h+4*t, l+2*t, "eeea", callback=[self.side], + move="right") + self.rectangularWall(w, h+2*t, "efFf", move="up") + self.rectangularWall(w, 0.25*l, "ffef", move="up") + self.rectangularWall(w, 0.25*l, "efef", move="up") + self.rectangularWall(w, self.hold_length, "efef", move="up")
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/wallpliersholder.html b/html/_modules/boxes/generators/wallpliersholder.html new file mode 100644 index 0000000..9d0a152 --- /dev/null +++ b/html/_modules/boxes/generators/wallpliersholder.html @@ -0,0 +1,179 @@ + + + + + + + + boxes.generators.wallpliersholder — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.wallpliersholder

+#!/usr/bin/env python3
+# Copyright (C) 2013-2019 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes.walledges import _WallMountedBox
+
+
[docs]class WallPliersHolder(_WallMountedBox): + """Bar to hang pliers on""" + + def __init__(self): + super().__init__() + + self.buildArgParser(sx="100*3", y=50, h=50, outside=True) + + self.argparser.add_argument( + "--angle", action="store", type=float, default=45, + help="bracing angle - less for more bracing") + + def brace(self, h, d, a, outside=False, move=None): + t = self.thickness + + tw = d + self.edges["b"].spacing() + self.edges["f"].spacing() + th = self.h_t + + if self.move(tw, th, move, True): + return + + self.moveTo(self.edges["b"].spacing()) + + r = d / 4 + l = (d + t - r) / math.sin(math.radians(a)) + + if outside: + self.polyline(t, (90-a, r), l, (a, r)) + self.edges["h"](h) + self.polyline(0, 90, d + 2*t, 90) + else: + self.polyline(0, (90-a, r), l, (a, r), 0, 90, t, -90) + self.edges["f"](h) + self.polyline(0, 90, d, 90) + self.edges["b"](h + (d+t-r) * math.tan(math.radians(90-a)) + r) + self.polyline(0, 90) + + self.move(tw, th, move) + + def frontCB(self): + t = self.thickness + posx = -t + for dx in self.sx[:-1]: + posx += dx + t + self.fingerHolesAt(posx, 0, self.h, 90) + + def backCB(self): + t = self.thickness + posx = -t + for dx in self.sx[:-1]: + posx += dx + t + self.wallHolesAt(posx, 0, self.h_t, 90) + + def render(self): + self.generateWallEdges() + + if self.outside: + self.sx = self.adjustSize(self.sx) + + sx, y, h = self.sx, self.y, self.h + t = self.thickness + + r = y / 4 + self.h_t = h + (y+t-r) * math.tan(math.radians(90-self.angle)) + r + + self.rectangularWall(sum(sx) + (len(sx)-1) * t, h, "efef", callback=[self.frontCB], move="up") + self.rectangularWall(sum(sx) + (len(sx)-1) * t, self.h_t, "eCec", callback=[self.backCB], move="up") + for i in range(len(sx)+1): + self.brace(h, y, self.angle, i<2, move="right")
+ +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/wallslottedholder.html b/html/_modules/boxes/generators/wallslottedholder.html new file mode 100644 index 0000000..e1cb9c6 --- /dev/null +++ b/html/_modules/boxes/generators/wallslottedholder.html @@ -0,0 +1,215 @@ + + + + + + + + boxes.generators.wallslottedholder — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.wallslottedholder

+#!/usr/bin/env python3
+# Copyright (C) 2013-2019 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes.walledges import _WallMountedBox
+
+class FrontEdge(edges.Edge):
+
+    def __call__(self, length, **kw):
+        ws = self.slot_width
+        wt = self.tool_width
+        ds = self.slot_depth
+        r1 = min(self.radius, ds/2, (wt-ws)/2)
+        r2 = min(self.radius, ws/2)
+        w = (wt-ws)/2 - r1
+        for i in range(self.number):
+            self.polyline(w, (90, r1), ds-r1-r2, (-90, r2), ws-2*r2,
+                          (-90, r2), ds-r1-r2, (90, r1), w)        
+
+
[docs]class WallSlottedHolder(_WallMountedBox): + """Wall tool holder with slots""" + + def __init__(self): + super().__init__() + + self.buildArgParser(h=120) + + self.argparser.add_argument( + "--slot_depth", action="store", type=float, default=50., + help="depth of slots from the front") + self.argparser.add_argument( + "--additional_depth", action="store", type=float, default=50., + help="depth behind the lots") + self.argparser.add_argument( + "--slot_width", action="store", type=float, default=5., + help="width of slots") + self.argparser.add_argument( + "--tool_width", action="store", type=float, default=35., + help="overall width for the tools") + #self.argparser.add_argument( + # "--angle", action="store", type=float, default=0., + # help="angle of the top - positive for leaning backwards") + self.argparser.add_argument( + "--radius", action="store", type=float, default=5., + help="radius of the slots at the front") + self.argparser.add_argument( + "--number", action="store", type=int, default=6, + help="number of tools/slots") + self.argparser.add_argument( + "--hooks", action="store", type=str, default="all", + choices=("all", "odds", "everythird"), + help="amount of hooks / braces") + + def brace(self, i): + n = self.number + if i in (0, n): + return True + # fold for symmetry + #if i > n//2: + # i = n - i + if self.hooks == "all": + return True + elif self.hooks == "odds": + return not (i % 2) + elif self.hooks == "everythird": + return not (i % 3) + + def braces(self): + return sum((self.brace(i) for i in range(self.number+1))) + + def backCB(self): + n = self.number + ws = self.slot_width + wt = self.tool_width + t = self.thickness + + d = min(2*t, (wt-ws)/4.) + self.wallHolesAt(d, 0, self.h, 90) + self.wallHolesAt(n*wt-d, 0, self.h, 90) + + for i in range(1, n): + if self.brace(i): + self.wallHolesAt(i*wt, 0, self.h, 90) + + def topCB(self): + n = self.number + ws = self.slot_width + wt = self.tool_width + t = self.thickness + l = self.additional_depth + self.slot_depth + + d = min(2*t, (wt-ws)/4.) + self.fingerHolesAt(d, 0, l, 90) + self.fingerHolesAt(n*wt-d, 0, l, 90) + + for i in range(1, n): + if self.brace(i): + self.fingerHolesAt(i*wt, 0, l, 90) + + def render(self): + self.generateWallEdges() + + t = self.thickness + l1, l2 = self.additional_depth, self.slot_depth + ws = self.slot_width + wt = self.tool_width + n = self.number + + self.rectangularWall(n*wt, self.h, "eeee", callback=[self.backCB], move="up") + self.rectangularWall(n*wt, l1+l2, [FrontEdge(self, None), "e","e","e"], callback=[self.topCB], move="up") + self.moveTo(0, t) + self.rectangularTriangle(l1+l2, self.h, "fbe", r=3*t, num=self.braces())
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/wallstairs.html b/html/_modules/boxes/generators/wallstairs.html new file mode 100644 index 0000000..5601d38 --- /dev/null +++ b/html/_modules/boxes/generators/wallstairs.html @@ -0,0 +1,173 @@ + + + + + + + + boxes.generators.wallstairs — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.wallstairs

+#!/usr/bin/env python3
+# Copyright (C) 2013-2019 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes.walledges import _WallMountedBox
+
+
[docs]class WallStairs(_WallMountedBox): + """Platforms in different heights e.g. for screw drivers""" + + description = """You are supposed to add holes or slots to the stair tops yourself using Inkscape or another vector drawing or CAD program. + +sh gives height of the stairs from front to back. Note that the overall width and height is bigger than the nominal values as walls and the protrusions are not included in the measurements. +""" + def __init__(self): + super().__init__() + + self.buildArgParser(sx="250/3", sy="40*3", sh="30:100:180") + self.argparser.add_argument( + "--braceheight", action="store", type=float, default=30, + help="height of the brace at the bottom back (in mm). Zero for none") + + def yWall(self, move=None): + t = self.thickness + x, sx, y, sy, sh = self.x, self.sx, self.y, self.sy, self.sh + + tw, th = sum(sy), max(sh) + t + + if self.move(tw, th, move, True): + return + + self.polyline(y-t, 90) + self.edges["f"](self.braceheight) + self.step(t) + self.edges["A"](sh[-1] - self.braceheight) + self.corner(90) + for i in range(len(sy)-1, 0, -1): + self.edges["f"](sy[i]) + self.step(sh[i-1]-sh[i]) + self.edges["f"](sy[0]) + self.polyline(0, 90, sh[0], 90) + + self.move(tw, th, move) + + def yCB(self, width): + t = self.thickness + posx = -0.5 * t + for dx in self.sx[:-1]: + posx += dx + t + self.fingerHolesAt(posx, 0, width, 90) + + + def render(self): + self.generateWallEdges() + + self.extra_height = 20 + t = self.thickness + sx, sy, sh = self.sx, self.sy, self.sh + self.x = x = sum(sx) + len(sx)*t - t + self.y = y = sum(sy) + + for w in sy: + self.rectangularWall( + x, w, "eheh", callback=[lambda:self.yCB(w)], move="up") + if self.braceheight: + self.rectangularWall( + x, self.braceheight, "eheh", + callback=[lambda:self.yCB(self.braceheight)], move="up") + + for i in range(len(sx) + 1): + self.yWall(move="right")
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/walltypetray.html b/html/_modules/boxes/generators/walltypetray.html new file mode 100644 index 0000000..0c917fc --- /dev/null +++ b/html/_modules/boxes/generators/walltypetray.html @@ -0,0 +1,217 @@ + + + + + + + + boxes.generators.walltypetray — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.walltypetray

+#!/usr/bin/env python3
+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes.lids import _TopEdge
+from boxes.walledges import _WallMountedBox
+
+
[docs]class WallTypeTray(_WallMountedBox, _TopEdge): + """Type tray - allows only continuous walls""" + + def __init__(self): + super().__init__() + self.addSettingsArgs(edges.StackableSettings) + self.buildArgParser("sx", "sy", "h", "hi", "outside", "bottom_edge") + self.argparser.add_argument( + "--back_height", action="store", type=float, default=0.0, + help="additional height of the back wall") + self.argparser.add_argument( + "--radius", action="store", type=float, default=0.0, + help="radius for strengthening walls with the hooks") + + + def xSlots(self): + posx = -0.5 * self.thickness + for x in self.sx[:-1]: + posx += x + self.thickness + posy = 0 + for y in self.sy: + self.fingerHolesAt(posx, posy, y) + posy += y + self.thickness + + def ySlots(self): + posy = -0.5 * self.thickness + for y in self.sy[:-1]: + posy += y + self.thickness + posx = 0 + for x in reversed(self.sx): + self.fingerHolesAt(posy, posx, x) + posx += x + self.thickness + + def xHoles(self): + posx = -0.5 * self.thickness + for x in self.sx[:-1]: + posx += x + self.thickness + self.fingerHolesAt(posx, 0, self.hi) + + def yHoles(self): + posy = -0.5 * self.thickness + for y in self.sy[:-1]: + posy += y + self.thickness + self.fingerHolesAt(posy, 0, self.hi) + + def render(self): + + self.generateWallEdges() + b = self.bottom_edge + + if self.outside: + self.sx = self.adjustSize(self.sx) + self.sy = self.adjustSize(self.sy) + self.h = self.adjustSize(self.h, b, e2=False) + if self.hi: + self.hi = self.adjustSize(self.hi, b, e2=False) + + x = sum(self.sx) + self.thickness * (len(self.sx) - 1) + y = sum(self.sy) + self.thickness * (len(self.sy) - 1) + h = self.h + bh = self.back_height + sameh = not self.hi + hi = self.hi = self.hi or h + t = self.thickness + + + # outer walls + # x sides + + self.ctx.save() + + # outer walls + self.rectangularWall(x, h, [b, "f", "e", "f"], callback=[self.xHoles], move="up") + self.rectangularWall(x, h+bh, [b, "C", "e", "c"], callback=[self.mirrorX(self.xHoles, x), ], move="up") + + # floor + if b != "e": + self.rectangularWall(x, y, "ffff", callback=[ + self.xSlots, self.ySlots], move="up") + + # Inner walls + + be = "f" if b != "e" else "e" + + for i in range(len(self.sy) - 1): + e = [edges.SlottedEdge(self, self.sx, be), "f", + edges.SlottedEdge(self, self.sx[::-1], "e", slots=0.5 * hi), "f"] + + self.rectangularWall(x, hi, e, move="up") + + # y walls + + # outer walls + self.trapezoidSideWall(y, h, h+bh, [b, "B", "e", "h"], radius=self.radius, callback=[self.yHoles, ], move="up") + self.moveTo(0, 8) + self.trapezoidSideWall(y, h+bh, h, [b, "h", "e", "b"], radius=self.radius, callback=[self.mirrorX(self.yHoles, y), ], move="up") + self.moveTo(0, 8) + + # inner walls + for i in range(len(self.sx) - 1): + e = [edges.SlottedEdge(self, self.sy, be, slots=0.5 * hi), + "f", "e", "f"] + self.rectangularWall(y, hi, e, move="up")
+ + + + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/wallwrenchholder.html b/html/_modules/boxes/generators/wallwrenchholder.html new file mode 100644 index 0000000..5e07526 --- /dev/null +++ b/html/_modules/boxes/generators/wallwrenchholder.html @@ -0,0 +1,185 @@ + + + + + + + + boxes.generators.wallwrenchholder — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.wallwrenchholder

+#!/usr/bin/env python3
+# Copyright (C) 2013-2019 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+from boxes.walledges import _WallMountedBox
+
+class SlottedEdge(edges.Edge):
+
+    def __call__(self, length, **kw):
+
+        n = self.number
+        t = self.thickness
+        
+        self.polyline(t, 45)
+
+        l = t
+        
+        for i in range(n):
+            w = self.min_width * ((n-i)/n) + self.max_width * (i / n)
+            s = self.min_strength * ((n-i)/n) + self.max_strength * (i / n)
+            if i == n-1:
+                self.polyline(w-s/2+2*s, (-180, s/2), w - 0.5*s,
+                              (180, s/2))
+                l += s *2 * 2**0.5
+            else:
+                self.polyline(w-s/2+2*s, (-180, s/2), w - 0.5*s,
+                              (135, s/2), self.extra_distance, (45, s/2))
+                l += s *2 * 2**0.5 + self.extra_distance
+        self.polyline(0, -45)
+        self.edge(length-l)
+
+
[docs]class WallWrenchHolder(_WallMountedBox): + """Hold a set of wrenches at a wall""" + + + def __init__(self): + super().__init__() + + # remove cli params you do not need + self.buildArgParser(x=100) + # Add non default cli params if needed (see argparse std lib) + self.argparser.add_argument( + "--depth", action="store", type=float, default=30.0, + help="depth of the sides (in mm)") + self.argparser.add_argument( + "--number", action="store", type=int, default=11, + help="number of wrenches (in mm)") + self.argparser.add_argument( + "--min_width", action="store", type=float, default=8.0, + help="width of smallest wrench (in mm)") + self.argparser.add_argument( + "--max_width", action="store", type=float, default=25.0, + help="width of largest wrench (in mm)") + self.argparser.add_argument( + "--min_strength", action="store", type=float, default=3.0, + help="strength of smallest wrench (in mm)") + self.argparser.add_argument( + "--max_strength", action="store", type=float, default=5.0, + help="strength of largest wrench (in mm)") + self.argparser.add_argument( + "--extra_distance", action="store", type=float, default=0.0, + help="additional distance between wrenches (in mm)") + + + def render(self): + self.generateWallEdges() + + h = ((self.min_strength + self.max_strength) * self.number * 2**0.5 + + self.extra_distance * (self.number - 1) + + self.max_width) + t = self.thickness + x = self.x-2*t + + self.rectangularWall(self.depth, h, + ["e", "B", "e", SlottedEdge(self, None)], + move="right") + self.rectangularWall(self.depth, h, + ["e", "B", "e", SlottedEdge(self, None)], + move="right") + self.rectangularWall(x, h, "eDed", + # callback=[lambda:self.fingerHolesAt(x/2, 0, h, 90)], + move="right")
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/generators/winerack.html b/html/_modules/boxes/generators/winerack.html new file mode 100644 index 0000000..e8ea8f7 --- /dev/null +++ b/html/_modules/boxes/generators/winerack.html @@ -0,0 +1,223 @@ + + + + + + + + boxes.generators.winerack — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.generators.winerack

+#!/usr/bin/env python3
+# Copyright (C) 2013-2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from boxes import *
+
+
[docs]class WineRack(Boxes): + """Honey Comb Style Wine Rack""" + + ui_group = "Shelf" + + def __init__(self): + Boxes.__init__(self) + + # Uncomment the settings for the edge types you use + self.addSettingsArgs(edges.FingerJointSettings) + + # remove cli params you do not need + self.buildArgParser(x=400, y=300, h=210) + # Add non default cli params if needed (see argparse std lib) + self.argparser.add_argument( + "--radius", action="store", type=float, default=46., + help="Radius of comb") + self.argparser.add_argument( + "--walls", action="store", type=str, default="all", + choices=("minimal", "no_verticals", "all"), + help="which of the honey comb walls to add") + + def hexFingerHoles(self, x, y, l, angle=90): + with self.saved_context(): + self.moveTo(x, y, angle) + self.moveTo(self.delta, 0, 0) + self.fingerHolesAt(0, 0, l-2*self.delta, 0) + + def wallCB(self, frontwall=False, backwall=False): + r = self.r + x, y, h = self.x, self.y, self.h + dx, dy = self.dx, self.dy + cx, cy = self.cx, self.cy + t = self.thickness + + if cy % 2: + ty = cy // 2 * (2*dy + 2*r) + 2*dy + r + else: + ty = cy // 2 * (2*dy + 2*r) + dy + + self.moveTo((x-dx*2*cx)/2, (y-ty) / 2) + + wmin = self.walls == "minimal" + + for i in range(cy//2 + cy % 2): + if not frontwall and self.walls == "all": + self.hexFingerHoles(0, (2*r+2*dy)*i+dy, r, 90) + for j in range(cx): + if not backwall: + self.hole(j*2*dx+dx, (2*r+2*dy)*i + r, dx-t) + if frontwall: + continue + self.hexFingerHoles(j*2*dx+dx, (2*r+2*dy)*i, r, 150) + self.hexFingerHoles(j*2*dx+dx, (2*r+2*dy)*i, r, 30) + if self.walls == "all": + self.hexFingerHoles(j*2*dx+2*dx, (2*r+2*dy)*i+dy, r, 90) + if wmin and i == cy//2: # top row + continue + if j>0 or not wmin: + self.hexFingerHoles(j*2*dx+dx, (2*r+2*dy)*i+r+2*dy, r, -150) + if j<cx-1 or not wmin: + self.hexFingerHoles(j*2*dx+dx, (2*r+2*dy)*i+r+2*dy, r, -30) + if i < cy//2: + for j in range(cx): + if not frontwall and self.walls == "all": + self.hexFingerHoles(j*2*dx+dx, (2*r+2*dy)*i+r+2*dy, r, 90) + if not backwall: + for j in range(1, cx): + self.hole(j*2*dx, (2*r+2*dy)*i + 2*r + dy, dx-t) + if cy % 2: + pass + else: + i = cy // 2 + for j in range(cx): + if frontwall or wmin: + break + if j > 0: + self.hexFingerHoles(j*2*dx+dx, (2*r+2*dy)*i, r, 150) + if j < cx -1: + self.hexFingerHoles(j*2*dx+dx, (2*r+2*dy)*i, r, 30) + + + def render(self): + x, y, h, radius = self.x, self.y, self.h, self.radius + + t = self.thickness + r = self.r = 2 * (radius + t) * math.tan(math.pi/6) + + self.dx = dx = r * math.cos(math.pi/6) + self.dy = dy = r * math.sin(math.pi/6) + self.cx = cx = int((x-2*t) // (2*dx)) + self.cy = cy = int((y-dy-t) // (r+dy)) + self.delta = 3**0.5/6.*t + + self.rectangularWall(x, y, callback=[self.wallCB], move="up") + self.rectangularWall(x, y, callback=[lambda:self.wallCB(backwall=True)], move="up") + self.rectangularWall(x, y, callback=[lambda:self.wallCB(frontwall=True)], move="up") + if self.walls == "all": + tc = (cy//2 + cy % 2) * (6 * cx + 1) + else: + tc = (cy//2 + cy % 2) * (4 * cx) + + if self.walls == "minimal": + tc -= 2 * (cy//2) # roofs of outer cells + + if cy % 2: + if self.walls == "all": + tc -= cx + else: + if self.walls != "minimal": + tc += 2 * cx - 2 # very top row + + self.partsMatrix(tc, cx, "up", self.rectangularWall, r-2*self.delta, h, "fefe")
+ + +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/parts.html b/html/_modules/boxes/parts.html new file mode 100644 index 0000000..308f223 --- /dev/null +++ b/html/_modules/boxes/parts.html @@ -0,0 +1,241 @@ + + + + + + + + boxes.parts — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.parts

+from math import *
+from boxes import vectors
+
+
[docs]def arcOnCircle(spanning_angle, outgoing_angle, r=1.0): + angle = spanning_angle + 2 * outgoing_angle + radius = r * sin(radians(0.5 * spanning_angle)) / sin(radians(180 - outgoing_angle - 0.5 * spanning_angle)) + return angle, abs(radius)
+ + +
[docs]class Parts: + def __init__(self, boxes): + self.boxes = boxes + + """ + def roundKnob(self, diameter, n=20, callback=None, move=""): + size = diameter+diameter/n + if self.move(size, size, move, before=True): + return + self.moveTo(size/2, size/2) + self.cc(callback, None, 0, 0) + + self.move(size, size, move) + """ + + def __getattr__(self, name): + return getattr(self.boxes, name) + +
[docs] def disc(self, diameter, hole=0, callback=None, move="", label=""): + """Simple disc + + :param diameter: diameter of the disc + :param hole: (Default value = 0) + :param callback: (Default value = None) called in the center + :param move: (Defaultvalue = None) + """ + size = diameter + r = diameter / 2.0 + + if self.move(size, size, move, before=True, label=label): + return + + self.moveTo(size / 2, size / 2) + + if hole: + self.hole(0, 0, hole / 2) + + self.cc(callback, None, 0, 0) + self.moveTo(r + self.burn, 0, 90) + self.corner(360, r, tabs=6) + self.move(size, size, move, label=label)
+ +
[docs] def waivyKnob(self, diameter, n=20, angle=45, hole=0, callback=None, move=""): + """Disc with a waivy edge to be easier to be gripped + + :param diameter: diameter of the knob + :param n: (Default value = 20) number of waves + :param angle: (Default value = 45) maximum angle of the wave + :param hole: (Default value = 0) + :param callback: (Default value = None) called in the center + :param move: (Defaultvalue = None) + """ + + if n < 2: + return + + size = diameter + pi * diameter / n + + if self.move(size, size, move, before=True): + return + + self.moveTo(size / 2, size / 2) + self.cc(callback, None, 0, 0) + + if hole: + self.hole(0, 0, hole / 2) + + self.moveTo(diameter / 2, 0, 90-angle) + a, r = arcOnCircle(360. / n / 2, angle, diameter / 2) + a2, r2 = arcOnCircle(360. / n / 2, -angle, diameter / 2) + + for i in range(n): + self.boxes.corner(a, r, tabs=(i % max(1, (n+1) // 6) == 0)) + self.boxes.corner(a2, r2) + + self.move(size, size, move)
+ +
[docs] def concaveKnob(self, diameter, n=3, rounded=0.2, angle=70, hole=0, + callback=None, move=""): + """Knob with dents to be easier to be gripped + + :param diameter: diameter of the knob + :param n: (Default value = 3) number of dents + :param rounded: (Default value = 0.2) proportion of circumferen remaining + :param angle: (Default value = 70) angle the dents meet the circumference + :param hole: (Default value = 0) + :param callback: (Default value = None) called in the center + :param move: (Defaultvalue = None) + """ + size = diameter + + if n < 2: + return + + if self.move(size, size, move, before=True): + return + + self.moveTo(size / 2, size / 2) + + if hole: + self.hole(0, 0, hole / 2) + + self.cc(callback, None, 0, 0) + self.moveTo(diameter / 2, 0, 90 + angle) + a, r = arcOnCircle(360. / n * (1 - rounded), -angle, diameter / 2) + + if abs(a) < 0.01: # avoid trying to make a straight line as an arc + a, r = arcOnCircle(360. / n * (1 - rounded), -angle - 0.01, diameter / 2) + + for i in range(n): + self.boxes.corner(a, r) + self.corner(angle) + self.corner(360. / n * rounded, diameter / 2, tabs= + (i % max(1, (n+1) // 6) == 0)) + self.corner(angle) + + self.move(size, size, move)
+ +
[docs] def ringSegment(self, r_outside, r_inside, angle, n=1, move=None): + """Ring Segment + + :param r_outside: outer radius + :param r_inside: inner radius + :param angle: anlge the segment is spanning + :param n: (Default value = 1) number of segments + :param move: (Defaultvalue = None) + """ + space = 360 * r_inside / self.spacing + n = min(n, 360 / (angle+space)) + + # XXX be smarter about space + if self.move(r_outside, r_outside, move, True): + return + + self.moveTo(r_outside) + for i in range(n): + self.polyline( + 0, (angle, r_outside), 0, 90, (r_outside-r_inside, 2), + 90, (angle, r_inside), 0, 90, (r_outside-r_inside, 2), + 90) + x, y = vectors.circlepoint(r_outside, math.radians(angle+space)) + self.moveTo(y, r_outside-x, angle+space) + self.move(r_outside, r_outside) + return n
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/pulley.html b/html/_modules/boxes/pulley.html new file mode 100644 index 0000000..d76fcdb --- /dev/null +++ b/html/_modules/boxes/pulley.html @@ -0,0 +1,240 @@ + + + + + + + + boxes.pulley — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.pulley

+"""
+// Parametric Pulley with multiple belt profiles
+// by droftarts January 2012
+
+// Based on pulleys by:
+// http://www.thingiverse.com/thing:11256 by me!
+// https://github.com/prusajr/PrusaMendel by Josef Prusa
+// http://www.thingiverse.com/thing:3104 by GilesBathgate
+// http://www.thingiverse.com/thing:2079 by nophead
+
+// dxf tooth data from http://oem.cadregister.com/asp/PPOW_Entry.asp?company=915217&elementID=07807803/METRIC/URETH/WV0025/F
+// pulley diameter checked and modelled from data at http://www.sdp-si.com/D265/HTML/D265T016.html
+"""
+from math import *
+from boxes.vectors import *
+
+
+
[docs]def tooth_spaceing_curvefit(teeth, b, c, d): + return ((c * teeth ** d) / (b + teeth ** d)) * teeth
+ + +
[docs]def tooth_spacing(teeth, tooth_pitch, pitch_line_offset): + return (2 * ((teeth * tooth_pitch) / (3.14159265 * 2) - pitch_line_offset))
+ + +
[docs]def mirrorx(points): + return [[-x, y] for x, y in points]
+ +
[docs]class Pulley: + + spacing = { + "MXL": (False, 2.032, 0.254), + "40DP": (False, 2.07264, 0.1778), + "XL": (False, 5.08, 0.254), + "H": (False, 9.525, 0.381), + "T2_5": (True, 0.7467, 0.796, 1.026), + "T5": (True, 0.6523, 1.591, 1.064), + "T10": (False, 10, 0.93), + "AT5": (True, 0.6523, 1.591, 1.064), + "HTD_3mm": (False, 3, 0.381), + "HTD_5mm": (False, 5, 0.5715), + "HTD_8mm": (False, 8, 0.6858), + "GT2_2mm": (False, 2, 0.254), + "GT2_3mm": (False, 3, 0.381), + "GT2_5mm": (False, 5, 0.5715), + } + + profile_data = { + "MXL": (0.508, 1.321), + "40DP": (0.457, 1.226), + "XL": (1.27, 3.051), + "H": (1.905, 5.359), + "T2_5": (0.7, 1.678), + "T5": (1.19, 3.264), + "T10": (2.5, 6.13), + "AT5": (1.19, 4.268), + "HTD_3mm": (1.289, 2.27), + "HTD_5mm": (2.199, 3.781), + "HTD_8mm": (3.607, 6.603), + "GT2_2mm": (0.764, 1.494), + "GT2_3mm": (1.169, 2.31), + "GT2_5mm": (1.969, 3.952), + } + + teeth = { "MXL" : [[-0.660421,-0.5],[-0.660421,0],[-0.621898,0.006033],[-0.587714,0.023037],[-0.560056,0.049424],[-0.541182,0.083609],[-0.417357,0.424392],[-0.398413,0.458752],[-0.370649,0.48514],[-0.336324,0.502074],[-0.297744,0.508035],[0.297744,0.508035],[0.336268,0.502074],[0.370452,0.48514],[0.39811,0.458752],[0.416983,0.424392],[0.540808,0.083609],[0.559752,0.049424],[0.587516,0.023037],[0.621841,0.006033],[0.660421,0],[0.660421,-0.5]], + "40DP" : [[-0.612775,-0.5],[-0.612775,0],[-0.574719,0.010187],[-0.546453,0.0381],[-0.355953,0.3683],[-0.327604,0.405408],[-0.291086,0.433388],[-0.248548,0.451049],[-0.202142,0.4572],[0.202494,0.4572],[0.248653,0.451049],[0.291042,0.433388],[0.327609,0.405408],[0.356306,0.3683],[0.546806,0.0381],[0.574499,0.010187],[0.612775,0],[0.612775,-0.5]], + "XL" : [[-1.525411,-1],[-1.525411,0],[-1.41777,0.015495],[-1.320712,0.059664],[-1.239661,0.129034],[-1.180042,0.220133],[-0.793044,1.050219],[-0.733574,1.141021],[-0.652507,1.210425],[-0.555366,1.254759],[-0.447675,1.270353],[0.447675,1.270353],[0.555366,1.254759],[0.652507,1.210425],[0.733574,1.141021],[0.793044,1.050219],[1.180042,0.220133],[1.239711,0.129034],[1.320844,0.059664],[1.417919,0.015495],[1.525411,0],[1.525411,-1]], + "H" : [[-2.6797,-1],[-2.6797,0],[-2.600907,0.006138],[-2.525342,0.024024],[-2.45412,0.052881],[-2.388351,0.091909],[-2.329145,0.140328],[-2.277614,0.197358],[-2.234875,0.262205],[-2.202032,0.334091],[-1.75224,1.57093],[-1.719538,1.642815],[-1.676883,1.707663],[-1.62542,1.764693],[-1.566256,1.813112],[-1.500512,1.85214],[-1.4293,1.880997],[-1.353742,1.898883],[-1.274949,1.905021],[1.275281,1.905021],[1.354056,1.898883],[1.429576,1.880997],[1.500731,1.85214],[1.566411,1.813112],[1.625508,1.764693],[1.676919,1.707663],[1.719531,1.642815],[1.752233,1.57093],[2.20273,0.334091],[2.235433,0.262205],[2.278045,0.197358],[2.329455,0.140328],[2.388553,0.091909],[2.454233,0.052881],[2.525384,0.024024],[2.600904,0.006138],[2.6797,0],[2.6797,-1]], + "T2_5" : [[-0.839258,-0.5],[-0.839258,0],[-0.770246,0.021652],[-0.726369,0.079022],[-0.529167,0.620889],[-0.485025,0.67826],[-0.416278,0.699911],[0.416278,0.699911],[0.484849,0.67826],[0.528814,0.620889],[0.726369,0.079022],[0.770114,0.021652],[0.839258,0],[0.839258,-0.5]], + "T5" : [[-1.632126,-0.5],[-1.632126,0],[-1.568549,0.004939],[-1.507539,0.019367],[-1.450023,0.042686],[-1.396912,0.074224],[-1.349125,0.113379],[-1.307581,0.159508],[-1.273186,0.211991],[-1.246868,0.270192],[-1.009802,0.920362],[-0.983414,0.978433],[-0.949018,1.030788],[-0.907524,1.076798],[-0.859829,1.115847],[-0.80682,1.147314],[-0.749402,1.170562],[-0.688471,1.184956],[-0.624921,1.189895],[0.624971,1.189895],[0.688622,1.184956],[0.749607,1.170562],[0.807043,1.147314],[0.860055,1.115847],[0.907754,1.076798],[0.949269,1.030788],[0.9837,0.978433],[1.010193,0.920362],[1.246907,0.270192],[1.273295,0.211991],[1.307726,0.159508],[1.349276,0.113379],[1.397039,0.074224],[1.450111,0.042686],[1.507589,0.019367],[1.568563,0.004939],[1.632126,0],[1.632126,-0.5]], + "T10" : [[-3.06511,-1],[-3.06511,0],[-2.971998,0.007239],[-2.882718,0.028344],[-2.79859,0.062396],[-2.720931,0.108479],[-2.651061,0.165675],[-2.590298,0.233065],[-2.539962,0.309732],[-2.501371,0.394759],[-1.879071,2.105025],[-1.840363,2.190052],[-1.789939,2.266719],[-1.729114,2.334109],[-1.659202,2.391304],[-1.581518,2.437387],[-1.497376,2.47144],[-1.408092,2.492545],[-1.314979,2.499784],[1.314979,2.499784],[1.408091,2.492545],[1.497371,2.47144],[1.581499,2.437387],[1.659158,2.391304],[1.729028,2.334109],[1.789791,2.266719],[1.840127,2.190052],[1.878718,2.105025],[2.501018,0.394759],[2.539726,0.309732],[2.59015,0.233065],[2.650975,0.165675],[2.720887,0.108479],[2.798571,0.062396],[2.882713,0.028344],[2.971997,0.007239],[3.06511,0],[3.06511,-1]], + "AT5" : [[-2.134129,-0.75],[-2.134129,0],[-2.058023,0.005488],[-1.984595,0.021547],[-1.914806,0.047569],[-1.849614,0.082947],[-1.789978,0.127073],[-1.736857,0.179338],[-1.691211,0.239136],[-1.653999,0.305859],[-1.349199,0.959203],[-1.286933,1.054635],[-1.201914,1.127346],[-1.099961,1.173664],[-0.986896,1.18992],[0.986543,1.18992],[1.099614,1.173664],[1.201605,1.127346],[1.286729,1.054635],[1.349199,0.959203],[1.653646,0.305859],[1.690859,0.239136],[1.73651,0.179338],[1.789644,0.127073],[1.849305,0.082947],[1.914539,0.047569],[1.984392,0.021547],[2.057906,0.005488],[2.134129,0],[2.134129,-0.75]], + "HTD_3mm" : [[-1.135062,-0.5],[-1.135062,0],[-1.048323,0.015484],[-0.974284,0.058517],[-0.919162,0.123974],[-0.889176,0.206728],[-0.81721,0.579614],[-0.800806,0.653232],[-0.778384,0.72416],[-0.750244,0.792137],[-0.716685,0.856903],[-0.678005,0.918199],[-0.634505,0.975764],[-0.586483,1.029338],[-0.534238,1.078662],[-0.47807,1.123476],[-0.418278,1.16352],[-0.355162,1.198533],[-0.289019,1.228257],[-0.22015,1.25243],[-0.148854,1.270793],[-0.07543,1.283087],[-0.000176,1.28905],[0.075081,1.283145],[0.148515,1.270895],[0.219827,1.252561],[0.288716,1.228406],[0.354879,1.19869],[0.418018,1.163675],[0.477831,1.123623],[0.534017,1.078795],[0.586276,1.029452],[0.634307,0.975857],[0.677809,0.91827],[0.716481,0.856953],[0.750022,0.792167],[0.778133,0.724174],[0.800511,0.653236],[0.816857,0.579614],[0.888471,0.206728],[0.919014,0.123974],[0.974328,0.058517],[1.048362,0.015484],[1.135062,0],[1.135062,-0.5]], + "HTD_5mm" : [[-1.89036,-0.75],[-1.89036,0],[-1.741168,0.02669],[-1.61387,0.100806],[-1.518984,0.21342],[-1.467026,0.3556],[-1.427162,0.960967],[-1.398568,1.089602],[-1.359437,1.213531],[-1.310296,1.332296],[-1.251672,1.445441],[-1.184092,1.552509],[-1.108081,1.653042],[-1.024167,1.746585],[-0.932877,1.832681],[-0.834736,1.910872],[-0.730271,1.980701],[-0.62001,2.041713],[-0.504478,2.09345],[-0.384202,2.135455],[-0.259708,2.167271],[-0.131524,2.188443],[-0.000176,2.198511],[0.131296,2.188504],[0.259588,2.167387],[0.384174,2.135616],[0.504527,2.093648],[0.620123,2.04194],[0.730433,1.980949],[0.834934,1.911132],[0.933097,1.832945],[1.024398,1.746846],[1.108311,1.653291],[1.184308,1.552736],[1.251865,1.445639],[1.310455,1.332457],[1.359552,1.213647],[1.39863,1.089664],[1.427162,0.960967],[1.467026,0.3556],[1.518984,0.21342],[1.61387,0.100806],[1.741168,0.02669],[1.89036,0],[1.89036,-0.75]], + "HTD_8mm" : [[-3.301471,-1],[-3.301471,0],[-3.16611,0.012093],[-3.038062,0.047068],[-2.919646,0.10297],[-2.813182,0.177844],[-2.720989,0.269734],[-2.645387,0.376684],[-2.588694,0.496739],[-2.553229,0.627944],[-2.460801,1.470025],[-2.411413,1.691917],[-2.343887,1.905691],[-2.259126,2.110563],[-2.158035,2.30575],[-2.041518,2.490467],[-1.910478,2.66393],[-1.76582,2.825356],[-1.608446,2.973961],[-1.439261,3.10896],[-1.259169,3.22957],[-1.069074,3.335006],[-0.869878,3.424485],[-0.662487,3.497224],[-0.447804,3.552437],[-0.226732,3.589341],[-0.000176,3.607153],[0.226511,3.589461],[0.447712,3.552654],[0.66252,3.497516],[0.870027,3.424833],[1.069329,3.33539],[1.259517,3.229973],[1.439687,3.109367],[1.608931,2.974358],[1.766344,2.825731],[1.911018,2.664271],[2.042047,2.490765],[2.158526,2.305998],[2.259547,2.110755],[2.344204,1.905821],[2.411591,1.691983],[2.460801,1.470025],[2.553229,0.627944],[2.588592,0.496739],[2.645238,0.376684],[2.720834,0.269734],[2.81305,0.177844],[2.919553,0.10297],[3.038012,0.047068],[3.166095,0.012093],[3.301471,0],[3.301471,-1]], + "GT2_2mm" : mirrorx([[0.747183,-0.5],[0.747183,0],[0.647876,0.037218],[0.598311,0.130528],[0.578556,0.238423],[0.547158,0.343077],[0.504649,0.443762],[0.451556,0.53975],[0.358229,0.636924],[0.2484,0.707276],[0.127259,0.750044],[0,0.76447],[-0.127259,0.750044],[-0.2484,0.707276],[-0.358229,0.636924],[-0.451556,0.53975],[-0.504797,0.443762],[-0.547291,0.343077],[-0.578605,0.238423],[-0.598311,0.130528],[-0.648009,0.037218],[-0.747183,0],[-0.747183,-0.5]]), + "GT2_3mm" : [[-1.155171,-0.5],[-1.155171,0],[-1.065317,0.016448],[-0.989057,0.062001],[-0.93297,0.130969],[-0.90364,0.217664],[-0.863705,0.408181],[-0.800056,0.591388],[-0.713587,0.765004],[-0.60519,0.926747],[-0.469751,1.032548],[-0.320719,1.108119],[-0.162625,1.153462],[0,1.168577],[0.162625,1.153462],[0.320719,1.108119],[0.469751,1.032548],[0.60519,0.926747],[0.713587,0.765004],[0.800056,0.591388],[0.863705,0.408181],[0.90364,0.217664],[0.932921,0.130969],[0.988924,0.062001],[1.065168,0.016448],[1.155171,0],[1.155171,-0.5]], + "GT2_5mm" : [[-1.975908,-0.75],[-1.975908,0],[-1.797959,0.03212],[-1.646634,0.121224],[-1.534534,0.256431],[-1.474258,0.426861],[-1.446911,0.570808],[-1.411774,0.712722],[-1.368964,0.852287],[-1.318597,0.989189],[-1.260788,1.123115],[-1.195654,1.25375],[-1.12331,1.380781],[-1.043869,1.503892],[-0.935264,1.612278],[-0.817959,1.706414],[-0.693181,1.786237],[-0.562151,1.851687],[-0.426095,1.9027],[-0.286235,1.939214],[-0.143795,1.961168],[0,1.9685],[0.143796,1.961168],[0.286235,1.939214],[0.426095,1.9027],[0.562151,1.851687],[0.693181,1.786237],[0.817959,1.706414],[0.935263,1.612278],[1.043869,1.503892],[1.123207,1.380781],[1.195509,1.25375],[1.26065,1.123115],[1.318507,0.989189],[1.368956,0.852287],[1.411872,0.712722],[1.447132,0.570808],[1.474611,0.426861],[1.534583,0.256431],[1.646678,0.121223],[1.798064,0.03212],[1.975908,0],[1.975908,-0.75]], + } + + def __init__(self, boxes): + self.boxes = boxes + +
[docs] @classmethod + def getProfiles(cls): + return list(sorted(cls.teeth.keys()))
+ +
[docs] def drawPoints(self, lines, kerfdir=1): + if kerfdir != 0: + lines = kerf(lines, self.boxes.burn * kerfdir) + self.boxes.ctx.save() + self.boxes.ctx.move_to(*lines[0]) + + for x, y in lines[1:]: + self.boxes.ctx.line_to(x, y) + + self.boxes.ctx.line_to(*lines[0]) + self.boxes.ctx.restore()
+ +
[docs] def diameter(self, teeth, profile): + if self.spacing[profile][0]: + return tooth_spaceing_curvefit(teeth, *self.spacing[profile][1:]) + + return tooth_spacing(teeth, *self.spacing[profile][1:])
+ + def __call__(self, teeth, profile, insideout=False, r_axle=None, + callback=None, move=""): + + # ******************************** + # ** Scaling tooth for good fit ** + # ******************************** + # To improve fit of belt to pulley, set the following constant. Decrease or increase by 0.1mm at a time. We are modelling the *BELT* tooth here, not the tooth on the pulley. Increasing the number will *decrease* the pulley tooth size. Increasing the tooth width will also scale proportionately the tooth depth, to maintain the shape of the tooth, and increase how far into the pulley the tooth is indented. Can be negative + + additional_tooth_width = 0.2 # mm + + # If you need more tooth depth than this provides, adjust the following constant. However, this will cause the shape of the tooth to change. + additional_tooth_depth = 0 # mm + + pulley_OD = self.diameter(teeth, profile) + + tooth_depth, tooth_width = self.profile_data[profile] + tooth_distance_from_centre = ((pulley_OD / 2) ** 2 - ((tooth_width + additional_tooth_width) / 2) ** 2) ** 0.5 + tooth_width_scale = (tooth_width + additional_tooth_width) / tooth_width + tooth_depth_scale = ((tooth_depth + additional_tooth_depth) / tooth_depth) + + if insideout: + pulley_OD += 2*tooth_depth * tooth_depth_scale + tooth_depth_scale *= -1 + + total_width = max(pulley_OD, 2*(r_axle or 0.0)) + + if self.boxes.move(total_width, total_width, move, before=True): + return + + self.boxes.moveTo(total_width / 2, total_width / 2) + self.boxes.cc(callback, None, 0.0, 0.0) + + if r_axle: + if insideout: + self.boxes.circle(0, 0, r_axle) + else: + self.boxes.hole(0, 0, r_axle) + + points = [] + for i in range(teeth): + m = [[tooth_width_scale, 0, 0], + [0, tooth_depth_scale, -tooth_distance_from_centre]] + m = mmul(m, rotm(i * 2 * pi / teeth)) + points.extend((vtransl(pt, m) for pt in self.teeth[profile][1:-1])) + + self.drawPoints(points, kerfdir=-1 if insideout else 1) + self.boxes.move(total_width, total_width, move)
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/robot.html b/html/_modules/boxes/robot.html new file mode 100644 index 0000000..174ba5f --- /dev/null +++ b/html/_modules/boxes/robot.html @@ -0,0 +1,275 @@ + + + + + + + + boxes.robot — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.robot

+import boxes
+
+__all__ = [
+    "RobotArg",
+    "RobotArmMM",
+    "RobotArmMm",
+    "RobotArmUU",
+    "RobotArmUu",
+    "RobotArmMu",
+]
+
+
[docs]class RobotArg: + + def __init__(self, includenone=False): + self.robotarms = [ + (name, globals()[name].__doc__[23:]) for name in __all__ + if name.startswith("RobotArm")] + if includenone: + self.robotarms[0:0] = [("none", "")] + + def __call__(self, arg): + return str(arg) + +
[docs] def choices(self): + return [name for name, descr in self.robotarms]
+ +
[docs] def html(self, name, default, translate): + options = "\n".join( + ("""<option value="%s"%s>%s %s</option>""" % + (name, ' selected="selected"' if name == default else "", + name, descr) for name, descr in self.robotarms)) + return """<select name="%s" size="1">\n%s</select>\n""" % (name, options)
+ + +class _RobotArm: + + def __init__(self, boxes, servo, servo2=None): + self.boxes = boxes + self.servo = servo + self.servo2 = servo2 or servo + + def __getattr__(self, name): + """Hack for easy access of Boxes methods""" + return getattr(self.boxes, name) + +
[docs]class RobotArmMM(_RobotArm): + """Robot arm segment with two parallel servos""" + def __call__(self, length, move=None): + t = self.thickness + w = self.servo.height + l = max(self.servo.length * 2, length + 2*self.servo.axle_pos) + + th = max(2 * t + l, 2*w + 4*t + self.spacing) + tw = 5 * (w + 2*self.thickness + self.spacing) + + if self.move(tw, th, move, True): + return + + self.rectangularWall(w, l, "FfFf", callback=[ + lambda:self.servo.top(w/2), None, + lambda:self.servo.top(w/2)], move="right") + self.rectangularWall(w, l, "FfFf", callback=[ + lambda:self.servo.bottom(w/2), None, + lambda:self.servo.bottom(w/2)], move="right") + self.rectangularWall(w, l, "FFFF", move="right") + self.rectangularWall(w, l, "FFFF", move="right") + self.rectangularWall(w, w, "ffff", callback=[ + lambda:self.servo.front(w/2)], move="up") + self.rectangularWall(w, w, "ffff", callback=[ + lambda:self.servo.front(w/2)], move="") + + self.move(tw, th, move)
+ +
[docs]class RobotArmMm(_RobotArm): + """Robot arm segment with two orthogonal servos""" + def __call__(self, length, move=None): + t = self.thickness + w = self.servo.height + w2 = self.servo2.height + l = max(self.servo.length * 2, length + 2*self.servo.axle_pos) + + th = max(2 * self.thickness + l, w + w2 + 4*t + self.spacing) + tw = 5 * (max(w, w2) + 2*self.thickness + self.spacing) + + if self.move(tw, th, move, True): + return + + self.rectangularWall(w2, l, "FfFf", callback=[ + lambda:self.servo.top(w2/2)], move="right") + self.rectangularWall(w2, l, "FfFf", callback=[ + lambda:self.servo.bottom(w2/2)], move="right") + self.rectangularWall(w, l, "FFFF", callback=[ + None, None, lambda:self.servo2.top(w/2)], move="right") + self.rectangularWall(w, l, "FFFF", callback=[ + None, None, lambda:self.servo2.bottom(w/2)], move="right") + self.rectangularWall(w2, w, "ffff", callback=[ + lambda:self.servo.front(w2/2)], move="up") + self.rectangularWall(w, w2, "ffff", callback=[ + lambda:self.servo2.front(w/2)], move="") + + self.move(tw, th, move)
+ +
[docs]class RobotArmUU(_RobotArm): + """Robot arm segment with two parallel sets of hinge knuckles""" + def __call__(self, length, move=None): + t = self.thickness + w = self.servo.hinge_width() + l = max(4*self.thickness, length - 2*t - 2*self.servo.height) + + th = max(2 * self.servo._edges["m"].spacing() + l, + 2*w + 4*t + self.spacing) + tw = 5 * (w + 2*self.thickness + self.spacing) + + if self.move(tw, th, move, True): + return + + iw = (0, 3, 4, 7) + e = self.servo.edges + self.rectangularWall(w, l, e("mFmF"), ignore_widths=iw, move="right") + self.rectangularWall(w, l, e("MFMF"), ignore_widths=iw, move="right") + self.rectangularWall(w, l, "FfFf", move="right") + self.rectangularWall(w, l, "FfFf", move="right") + self.rectangularWall(w, w, "ffff", callback=[ + lambda: self.hole(w/2, w/2, 6)], move="up") + self.rectangularWall(w, w, "ffff", callback=[ + lambda: self.hole(w/2, w/2, 6)], move="") + + self.move(tw, th, move)
+ +
[docs]class RobotArmUu(_RobotArm): + """Robot arm segment with two orthogonal sets of hinge knuckles""" + def __call__(self, length, move=None): + t = self.thickness + w = self.servo.hinge_width() + w2 = self.servo2.hinge_width() + l = max(4*self.thickness, length - 2*t - 2*self.servo.height) + + th = max(self.thickness + self.servo._edges["m"].spacing() + l, + 2*w + self.thickness + 4 * self.edges["f"].spacing()) + tw = 5 * (w + 2*self.thickness + self.spacing) + + if self.move(tw, th, move, True): + return + iw = (3, 4) + e = self.servo.edges + self.rectangularWall(w2, l, e("nfFf"), move="right") + self.rectangularWall(w2, l, e("NfFf"), move="right") + self.rectangularWall(w, l, e("FFmF"), ignore_widths=iw, move="right") + self.rectangularWall(w, l, e("FFMF"), ignore_widths=iw, move="right") + self.rectangularWall(w2, w, "ffff", callback=[ + lambda: self.hole(w2/2, w/2, 6)], move="up") + self.rectangularWall(w2, w, "ffff", callback=[ + lambda: self.hole(w2/2, w/2, 6)], move="") + + self.move(tw, th, move)
+ +
[docs]class RobotArmMu(_RobotArm): + """Robot arm segment with a servo and an orthogonal sets of hinge knuckles""" + def __call__(self, length, move=None): + t = self.thickness + w = self.servo.height + w2 = self.servo2.hinge_width() + l = max(self.servo.length, length + self.servo.axle_pos - self.servo.height - t) + + th = max(t + l + self.servo2._edges["m"].spacing(), + w + w2 + self.thickness + 4 * self.edges["f"].spacing()) + tw = 5 * (w + 2*self.thickness + self.spacing) + + if self.move(tw, th, move, True): + return + + e = self.servo2.edges + iw = (3, 4) + self.rectangularWall(w2, l, "FfFf", callback=[ + lambda:self.servo.top(w2/2)], move="right") + self.rectangularWall(w2, l, "FfFf", callback=[ + lambda:self.servo.bottom(w2/2)], move="right") + self.rectangularWall(w, l, e("FFmF"), ignore_widths=iw, move="right") + self.rectangularWall(w, l, e("FFMF"), ignore_widths=iw, move="right") + self.rectangularWall(w2, w, "ffff", callback=[ + lambda:self.servo.front(w2/2)], move="up") + self.rectangularWall(w2, w, "ffff", callback=[ + lambda: self.hole(w2/2, w/2, 6)], move="") + + self.move(tw, th, move)
+ +# class RobotArmMU(_RobotArm): +
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/servos.html b/html/_modules/boxes/servos.html new file mode 100644 index 0000000..07207b4 --- /dev/null +++ b/html/_modules/boxes/servos.html @@ -0,0 +1,208 @@ + + + + + + + + boxes.servos — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.servos

+import boxes.vectors
+import math
+
+
[docs]class EyeEdge(boxes.edges.FingerHoleEdge): + + char = "m" + + def __init__(self, boxes, servo, fingerHoles=None, driven=False, + outset=False, **kw): + self.servo = servo + self.outset = outset + self.driven = driven + super(EyeEdge, self).__init__(boxes, fingerHoles, **kw) + + def __call__(self, length, bedBolts=None, bedBoltSettings=None, **kw): + t = self.fingerHoles.settings.thickness + dist = self.fingerHoles.settings.edge_width + + pos_axle = self.servo.hinge_depth() + self.ctx.save() + self.hole(length/2.0, -pos_axle, + self.servo.axle/2.0 if self.driven else + self.servo.servo_axle/2.0) + if self.outset: + self.fingerHoles(t, self.thickness / 2, length-2*t, 0) + else: + self.fingerHoles(0, self.thickness / 2, length, 0) + self.ctx.restore() + r = self.servo.servo_axle * 2 + a, l = boxes.vectors.tangent(length/2, pos_axle, r) + angle = math.degrees(a) + self.polyline(0, -angle, l, (2*angle, r), l, -angle, 0) + +
[docs] def startwidth(self): + return self.fingerHoles.settings.thickness
+ +
[docs] def margin(self): + return self.servo.hinge_depth() + self.fingerHoles.settings.thickness + self.servo.servo_axle * 2
+ +
[docs]def buildEdges(boxes, servo, chars="mMnN"): + result = {} + for n, char in enumerate(chars): + e = EyeEdge(boxes, servo, outset=(n<2), driven=(n % 2)) + e.char = char + result[char] = e + return result
+ +
[docs]class ServoArg: + + def __init__(self, includenone=False): + self.servos = ["Servo9g"] + if includenone: + self.servos[0:0] = ["none"] + + def __call__(self, arg): + return str(arg) + +
[docs] def choices(self): + return [name for name in self.servos]
+ +
[docs] def html(self, name, default, translate): + options = "\n".join( + ("""<option value="%s"%s>%s</option>""" % + (name, ' selected="selected"' if name == default else "", + name) for name in self.servos)) + return """<select name="%s" size="1">\n%s</select>\n""" % (name, options)
+ + +
[docs]class Servo: + def __init__(self, boxes, axle=3): + self.boxes = boxes + self.axle = axle + self._edges = buildEdges(boxes, self) + +
[docs] def edges(self, edges): + return [self._edges.get(e, e) for e in edges]
+ +
[docs]class Servo9g(Servo): + + height = 22.5 + length = 28.0 # one tab in the wall + width = 12.0 + axle_pos = 6.0 + servo_axle = 4.6 # 6.9 for servo arm + +
[docs] def top(self, x=0.0, y=0.0, angle=90.0): + self.boxes.moveTo(x, y, angle) + self.boxes.hole(6, 0, 6) + self.boxes.hole(12, 0, 3)
+ +
[docs] def bottom(self, x=0.0, y=0.0, angle=90.0): + self.boxes.moveTo(x, y, angle) + self.boxes.hole(6, 0, self.axle/2.0)
+ +
[docs] def front(self, x=0.0, y=0.0, angle=90.0): + self.boxes.moveTo(x, y, angle) + self.boxes.rectangularHole(5.4, 0, 2.4, 12) + self.boxes.rectangularHole(17, 0, 4, 16)
+ +
[docs] def hinge_width(self): + return self.height + self.boxes.thickness + 4.5
+ +
[docs] def hinge_depth(self): + return self.height # XXX
+ +
[docs]class Servo9gt(Servo9g): + height = 35 + +
[docs] def top(self, x=0.0, y=0.0, angle=90.0): + self.boxes.moveTo(x, y, angle) + self.boxes.hole(6, 0, 6) + self.boxes.hole(12, 0, 5)
+ +
[docs] def bottom(self, x=0.0, y=0.0, angle=90.0): + self.boxes.moveTo(x, y, angle) + self.boxes.hole(6, 0, self.axle)
+ +
[docs] def front(self, x=0.0, y=0.0, angle=90.0): + self.boxes.moveTo(x, y, angle) + self.boxes.rectangularHole(5.4, 0, 2.4, 12)
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/svgutil.html b/html/_modules/boxes/svgutil.html new file mode 100644 index 0000000..203d09b --- /dev/null +++ b/html/_modules/boxes/svgutil.html @@ -0,0 +1,174 @@ + + + + + + + + boxes.svgutil — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.svgutil

+#!/usr/bin/env python3
+# Copyright (C) 2016 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import re, datetime
+
+from xml.etree import cElementTree as ElementTree
+ElementTree.register_namespace("","http://www.w3.org/2000/svg")
+ElementTree.register_namespace("xlink", "http://www.w3.org/1999/xlink")
+
+unit2mm = {"mm" : 1.0,
+           "cm" : 10.0,
+           "in" : 25.4,
+           "px" : 90.0/25.4,
+           "pt" : 90.0/25.4/1.25,
+           "pc" : 90.0/25.4/15,
+}
+
+
[docs]def getSizeInMM(tree): + root = tree.getroot() + m = re.match(r"(-?\d+\.?\d*)(\D+)", root.get("height")) + height, units = m.groups() + height = float(height) * unit2mm.get(units, 1.0) + + m = re.match(r"(-?\d+\.?\d*)(\D+)", root.get("width")) + width, units = m.groups() + width = float(width) * unit2mm.get(units, 1.0) + + return width, height
+ +
[docs]def getViewBox(tree): + root = tree.getroot() + m = re.match(r"\s*(-?\d+\.?\d*)\s+" + "(-?\d+\.?\d*)\s+" + "(-?\d+\.?\d*)\s+" + "(-?\d+\.?\d)\s*", root.get("viewBox")) + + return [float(m) for m in m.groups()]
+ +
[docs]def ticksPerMM(tree): + width, height = getSizeInMM(tree) + x1, y1, x2, y2 = getViewBox(tree) + + return x2/width, y2/height
+ +
[docs]def svgMerge(box, inkscape, output): + + src_tree = ElementTree.parse(box) + dest_tree = ElementTree.parse(inkscape) + dest_root = dest_tree.getroot() + + src_width, src_height = getSizeInMM(src_tree) + dest_width, dest_height = getSizeInMM(dest_tree) + + src_scale_x, src_scale_y = ticksPerMM(src_tree) + dest_scale_x, dest_scale_y = ticksPerMM(dest_tree) + + scale_x = dest_scale_x / src_scale_x + scale_y = dest_scale_y / src_scale_y + + src_view = getViewBox(src_tree) + + off_x = src_view[0] * -scale_x + off_y = (src_view[1]+src_view[3]) * -scale_y + dest_height * scale_y + + for el in src_tree.getroot(): + import sys + dest_root.append(el) + if el.tag.endswith("g"): + el.set("transform", "matrix(%f,0,0,%f, %f, %f)" % ( + scale_x, scale_y, off_x, off_y)) + + # write the xml file + ElementTree.ElementTree(dest_root).write(output, encoding='utf-8', xml_declaration=True)
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/boxes/vectors.html b/html/_modules/boxes/vectors.html new file mode 100644 index 0000000..17cf997 --- /dev/null +++ b/html/_modules/boxes/vectors.html @@ -0,0 +1,206 @@ + + + + + + + + boxes.vectors — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for boxes.vectors

+# Copyright (C) 2013-2014 Florian Festi
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+import math
+
+
+
[docs]def normalize(v): + "set lenght of vector to one" + l = (v[0] ** 2 + v[1] ** 2) ** 0.5 + if l == 0.0: + return (0.0, 0.0) + return (v[0] / l, v[1] / l)
+ + +
[docs]def vlength(v): + return (v[0] ** 2 + v[1] ** 2) ** 0.5
+ + +
[docs]def vclip(v, length): + l = vlength(v) + if l > length: + return vscalmul(v, length / l) + return v
+ + +
[docs]def vdiff(p1, p2): + "vector from point1 to point2" + return (p2[0] - p1[0], p2[1] - p1[1])
+ + +
[docs]def vadd(v1, v2): + "Sum of two vectors" + return (v1[0] + v2[0], v1[1] + v2[1])
+ + +
[docs]def vorthogonal(v): + "orthogonal vector" + "Orthogonal vector" + return (-v[1], v[0])
+ + +
[docs]def vscalmul(v, a): + "scale vector by a" + return (a * v[0], a * v[1])
+ + +
[docs]def dotproduct(v1, v2): + "Dot product" + return v1[0] * v2[0] + v1[1] * v2[1]
+ +
[docs]def circlepoint(r, a): + return (r * math.cos(a), r * math.sin(a))
+ +
[docs]def tangent(x, y, r): + "angle and length of a tangent to a circle at x,y with raduis r" + l1 = vlength((x, y)) + a1 = math.atan2(y, x) + a2 = math.asin(r / l1) + l2 = math.cos(a2) * l1 + + return (a1+a2, l2)
+ +
[docs]def rotm(angle): + "Rotation matrix" + return [[math.cos(angle), -math.sin(angle), 0], + [math.sin(angle), math.cos(angle), 0]]
+ + +
[docs]def vtransl(v, m): + m0, m1 = m + return [m0[0] * v[0] + m0[1] * v[1] + m0[2], + m1[0] * v[0] + m1[1] * v[1] + m1[2]]
+ + +
[docs]def mmul(m0, m1): + result = [[0, ] * len(m0[0]) for i in range(len(m0))] + for i in range(len(m0[0])): + for j in range(len(m0)): + for k in range(len(m0)): + result[j][i] += m0[k][i] * m1[j][k] + return result
+ + +
[docs]def kerf(points, k, closed=True): + """Outset points by k + Assumes a closed loop of points + """ + result = [] + lp = len(points) + + for i in range(len(points)): + # get normalized orthogonals of both segments + v1 = vorthogonal(normalize(vdiff(points[i - 1], points[i]))) + v2 = vorthogonal(normalize(vdiff(points[i], points[(i + 1) % lp]))) + + if not closed: + if i == 0: + v1 = v2 + if i == lp-1: + v2 = v1 + # direction the point has to move + d = normalize(vadd(v1, v2)) + # cos of the half the angle between the segments + cos_alpha = dotproduct(v1, d) + result.append(vadd(points[i], vscalmul(d, -k / cos_alpha))) + + return result
+
+ +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_modules/index.html b/html/_modules/index.html new file mode 100644 index 0000000..5dd6dfa --- /dev/null +++ b/html/_modules/index.html @@ -0,0 +1,220 @@ + + + + + + + + Overview: module code — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

All modules for which code is available

+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/_sources/CONTRIBUTING.rst.txt b/html/_sources/CONTRIBUTING.rst.txt new file mode 100644 index 0000000..ac7b6bc --- /dev/null +++ b/html/_sources/CONTRIBUTING.rst.txt @@ -0,0 +1 @@ +.. include:: ../../CONTRIBUTING.rst diff --git a/html/_sources/README.rst.txt b/html/_sources/README.rst.txt new file mode 100644 index 0000000..4d547b7 --- /dev/null +++ b/html/_sources/README.rst.txt @@ -0,0 +1,2 @@ +.. include:: ../../README.rst + :end-before: Documentation diff --git a/html/_sources/api_architecture.rst.txt b/html/_sources/api_architecture.rst.txt new file mode 100644 index 0000000..34eddc4 --- /dev/null +++ b/html/_sources/api_architecture.rst.txt @@ -0,0 +1,96 @@ +Architecture +------------ + +Boxes.py it structured into several distinct tiers. + +User Interfaces +............... + +User interfaces allow users to render the different generators. They +handle the parameters of Generators and convert them to a readable +form. The user interfaces are located in `scripts/`. Currently there is + +* scripts/boxes -- the command line interface +* scripts/boxesserver -- the web interface +* scripts/boxes2inx -- generates Inkscape extensions +* scripts/boxes_example.ipynb -- Jupyter notebook + + +Generators +.......... + +A (box) generator is an sub class of boxes.Boxes. It generates one +drawing. The sub classes over load .__init__() to set their parameters +and implement .render() that does the actual drawing. + +Generators are found in ``boxes/generators/``. They are included into +the web UI and the CLI tool by the name of their class. So whenever +you copy either an existing generator or the sceleton in +``boxes/generators/_template.py`` you need to change the name of the +main class first. + +Parts +..... + +Parts are a single call that draws something according to a set of parameters. +There is a number of standard parts. Their typical params are +explained in the API docs. + +Only real requirement for a part it supporting the move parameter for +placement. + +Part Callbacks +++++++++++++++ + +Most parts support callbacks - either one in the middle for round +parts or one for each edge. They allow placing holes or other features +on the part. + +Navigation and Turtle Graphics +.............................. + +Many drawing commands in Boxes.py are Turtle Graphics commands. They +start at the current position and in the current direction and move +the coordinate system with them. This way the absolute coordinates are +never used and placement and movement is always relative to the +current position. + +There are a few functions to move the origin to a convenient position +or to return to a previously saved position. + +Edges +..... + +Edges are turtle graphic commands. But they have been elevated to +proper Classes to handle outsets. They can be passed as parameters to parts. +There is a set of standard edges found in ``.edges``. They are +associated with a single char which can be used instead of the +Edge object itself at most places. This allows passing the edge +description of a part as a string. + +Turtle graphics +............... + +There are a few turtle graphics commands that do the actual +drawing. Corners with an positive angle (going counter clockwise) +close the part while negative angles (going clockwise) create protrusions. +This is inversed for holes which need to be drawn clockwise. + +Getting this directions right is important to make the burn correction +(aka kerf) work properly. + +Simple drawing commands +....................... + +These also are simple drawing commands. Some of them get ``x``, ``y`` and +``angle`` parameters to draw somewhere specific. Some just draw right +at the current coordinate origin. Often these commands create holes or +hole patterns. + +Back end +........ + +Boxes.py used to use cairo as graphics library. It now uses its own - +pure Python - back end. It is not fully encapsulated +within the drawing methods of the Boxes class. Although this is the +long term goal. Boxes.ctx is the context all drawing is made on. diff --git a/html/_sources/api_arguments.rst.txt b/html/_sources/api_arguments.rst.txt new file mode 100644 index 0000000..27bb2ca --- /dev/null +++ b/html/_sources/api_arguments.rst.txt @@ -0,0 +1,76 @@ +Generator Arguments +------------------- + +Boxes.py uses the ``argparse`` standard library for handling the +arguments for the generators. It is used directly for the ``boxes`` +command line tool. But it also handles -- with some additional code -- +the web interface and the Inkscape extensions. To make this work one +has to limit the kind of parameters used. Boxes.py supports the +following types: + + * ``int`` + * ``float`` + * ``str`` + * ``boxes.boolarg`` -- an alternative to ``bool`` that works with the + web interface + * ``boxes.argparseSections`` -- multiple lengths e.g. for dividing up + a box in one direction + +and + +.. autoclass:: boxes.ArgparseEdgeType + +For the standard types there is code to create HTML and Inkscape +extensions. The other types can have ``.html()`` and ``.inx()`` +methods. + +The argument parser need to be built in the ``.__init__()`` method +after calling the method of the super class. Have a look at + +.. automethod:: boxes.generators._template.BOX.__init__ + +As many arguments are used over and over there is a function that can +add the most common ones: + +.. automethod:: boxes.Boxes.buildArgParser + +Check the source for details about the single arguments. + +Other arguments can be added with the normal argparser API - namely + +.. automethod:: argparse.ArgumentParser.add_argument + +of the ``Boxes.argparser`` attribute. + +Edge style arguments +.................... + +Edges that work together share a Settings class (and object). These +classes can create ``argparse`` groups: + +.. automethod:: boxes.edges.Settings.parserArguments + +See + +.. automethod:: boxes.generators._template.BOX.__init__ + +for a list of possible edge settings. These regular settings are used +in the standard edge instances used everywhere. For special edge +instances you can call them with a ``prefix`` parameter. But you then +need to deal with the results on your own. + +Default Arguments +................. + +The :ref:`default-args` get added automatically by the super class's +constructor. + +Accessing the Arguments +....................... + +For convenience content of the arguments are written to attributes of +the Boxes instance before ``.render()`` is called. This is done by +``Boxes.parseArgs``. But most people won't need to care as this is +handled by the frame work. Be careful to **not overwrite important +methods or attributes by using conflicting argument names**. + diff --git a/html/_sources/api_burn.rst.txt b/html/_sources/api_burn.rst.txt new file mode 100644 index 0000000..d7fbab9 --- /dev/null +++ b/html/_sources/api_burn.rst.txt @@ -0,0 +1,79 @@ +Burn correction +=============== + +The burn correction -- aka kerf -- is done in two separate steps. The +first mechanism is used during drawing. After rendering there is +a post processing step that replaces the inverted arcs of the inner corners by +Bezier loops that can be cut in a continous motion. + +The first mechanism is integrated into the low level +commands of Boxes.py. So for the most part developers do not need to +care about it. Nevertheless they need to understand how it works to +catch the places the do need to care. + +Burn correction is done by increasing the radius of all outer +corners. This moves all the straight lines outward by the same +amount. This has the added benefit of not needing to change the length +of the straight lines -- making them independent of the adjacent +angles. An issue arises when it comes to inner corners. If they do +have a radius reducing it by the burn value does the right thing. But +for small radii and sharp corners (radius zero) this results in a +negative values. It turns out flipping over the arc for negative radii +allows keeping the lengths of the straight lines unchanged. So this is +what Boxes.py does: + +.. image:: burn.svg + +This results in the straight lines touching the piece. This would lead to +overcuts that are not as nice as proper dog bones as might be used by +a dedicated CAM software. But as Boxes.py is meant to be used for laser +cutting this deemed acceptable for a long time: + +.. image:: overcuts.svg + +Programmer's perspective +------------------------ + +For this to work it is important that outside is drawn in a counter +clock wise direction while holes are drawn in a clock wise direction. + +:py:meth:`boxes.Boxes.corner` adjusts the radius automatically +according to **.burn**. This propagates to higher level +functions. Parts shipped with Boxes.py do take the +burn out-set into account and execute callbacks at the correct position. + +In case developers move to a feature inside of a part or executing +callbacks while implementing a part they need to be aware of the burn +correction. :py:meth:`boxes.Boxes.cc` does correct for the out-set if +called without an **y** parameter. But if a value is given one has to +add **self.burn** to compensate. Note that the **x** value typically +does not have to be corrected as the callbacks are executed from right +underneath the part. + +A similar approach is necessary when moving to a feature drawn inside +the part without the use of callbacks. Here you typically have to +correct for the out-set at the outside of the part and again for in-set +of the hole one is about to cut. This can be done in **x** or **y** +direction depending on whether the cut ist started vertical or +horizontally. + +Replacing the inverted arcs +--------------------------- + +The inverted arcs have several drawbacks. For one they remove more +material than needed. This is not a big deal for laser cutters. But if +the boxes are cut with a CNC milling machine that can be +annoying. Another drawback is that the direction is reversed twice +which requires the tool (typically the laser head) to come to a total stop. + +To solve this issue all paths are scanned for intersecting lines that +are connected by an inverted arc. There the lines are shortened to the +intersection point and the arc is replaced by a Bezier loop that is +continues the lines and loops on the outside of the corner. That way +the path still removes additional material to make sure the full +inner corner is cleared out. The current implementation uses the +former end points of the lines as control points. This gives +reasonable results but errs on the save side. The amount of material +removed can probably be further optimized. + +.. image:: burn2.svg diff --git a/html/_sources/api_drawing.rst.txt b/html/_sources/api_drawing.rst.txt new file mode 100644 index 0000000..f0dabdc --- /dev/null +++ b/html/_sources/api_drawing.rst.txt @@ -0,0 +1,90 @@ +Drawing commands +================ + + +Turtle Graphics commands +------------------------ + +These commands all move the coordinate system with them. + +.. automethod:: boxes.Boxes.edge +.. automethod:: boxes.Boxes.corner +.. automethod:: boxes.Boxes.curveTo +.. automethod:: boxes.Boxes.polyline + +Special Functions +................. + +.. automethod:: boxes.Boxes.bedBoltHole + +Latch and Grip +.............. + +These should probably be Edge classes. But right now they are still functions. + +.. automethod:: boxes.Boxes.grip +.. automethod:: boxes.Boxes.latch +.. automethod:: boxes.Boxes.handle + +Tab support +........... + +Tabs are small interuptions in the border of a part to keep it in +place. They are enabled with the **tabs** parameter. All +**Edges** automatically create about two tabs. So parts like +:py:meth:`boxes.Boxes.rectangularWall` will have 8 tabs holding them +in place. Because of this developers often don't need to be concerned +about tabs. But some part may be completely drawn by low level Turtle +Graphics commands. For those both :py:meth:`boxes.Boxes.edge` and +:py:meth:`boxes.Boxes.corner` do support a **tabs** parameter. In +addition the length of the line segments in :py:meth:`boxes.Boxes.polyline` can +be given as a tuple **(length, tabs)**. + +Draw Commands +------------- + +These commands do not change the coordinate system but get the +coordinates passed as parameters. All of them are either som sort of +hole or text. These artifacts are placed somewhere independently of +some continuous outline of the part their on. + +.. automethod:: boxes.Boxes.hole +.. automethod:: boxes.Boxes.rectangularHole +.. automethod:: boxes.Boxes.dHole +.. automethod:: boxes.Boxes.flatHole +.. automethod:: boxes.Boxes.text +.. automethod:: boxes.Boxes.NEMA +.. automethod:: boxes.Boxes.TX +.. automethod:: boxes.Boxes.flex2D +.. py:class:: NutHole + +An instance is available as **boxes.Boxes.nutHole()** + +An instance of + +.. autoclass:: boxes.edges.FingerHoles + :noindex: + +is accessible as **Boxes.fingerHolesAt**. + + +Hexagonal Hole patterns +....................... + +Hexagonal hole patterns are one way to have some ventilation for +housings made with Boxes.py. Right now both ``.rectangularWall()`` +and ``.roundedPlate()`` do supports this pattern directly by passing +the parameters to the calls. For other use cases these more low level +methods can be used. + +For now this is the only supported pattern for ventilation slots. More +may be added in the future. + +There is a global Boxes.hexHolesSettings object that is used if no settings are +passed. It currently is just a tuple of (r, dist, style) defaulting to +(5, 3, 'circle') but might be replace by a Settings instance in the future. + +.. automethod:: boxes.Boxes.hexHolesRectangle +.. automethod:: boxes.Boxes.hexHolesCircle +.. automethod:: boxes.Boxes.hexHolesPlate +.. automethod:: boxes.Boxes.hexHolesHex diff --git a/html/_sources/api_edges.rst.txt b/html/_sources/api_edges.rst.txt new file mode 100644 index 0000000..b1115f7 --- /dev/null +++ b/html/_sources/api_edges.rst.txt @@ -0,0 +1,193 @@ +Edges +===== + +Edges are what makes Boxes.py work. They draw a -- more or less -- straight +border to the current piece. They are part of the turtle graphics part +of Boxes.py. This means they start at the current position and current +direction and move the current position to the end of the edge. + +Edge instances have a Settings object associated with them that keeps +the details about how the edge should look like. Edges that are +supposed to work together share the same Settings object to ensure +they fit together - assuming they have the same length. Most edges are +symetrical to unsure they fit together even when drawn from different +directions. Although there are a few exception - mainly edges that +provide special features like hinges. + +As edges started out as methods of the main Boxes class they still are +callables. It turned out that the edges need to provide a bit more +information to allow the surrounding code to handle them +properly. When drawing an Edge there is a virtual straight line that +is the border the shape of the part (e.g. an rectangle). But the +actual Edge has often to be drawn elsewhere. Best example if probably +the ``F`` Edge that matches the normal finger joints. It has to start +one material thickness outside of the virual border of the part so the +cutouts for the opposing fingers just touch the border. The Edge +classes have a number of methods to deal with these kind of offsets. + +A set of instances are kept the ``.edges`` attribute of the +``Boxes`` class. It is a dict with strings of length one as keys: + +* aAbB : reserved to be used in generators +* c : ClickConnector +* C : ClickEdge +* d : DoveTailJoint +* D : DoveTailJointCounterPart +* e : Edge +* E : OutSetEdge +* f : FingerJointEdge +* F : FingerJointEdgeCounterPart +* g : GrippingEdge +* G : MountingEdge +* h : FingerHoleEdge +* ijk : Hinge (start, end, both sides) +* IJK : HingePin (start, end, both sides) +* L : LidHoleEdge +* l : LidEdge +* M : LidSideLeft +* m : LidLeft +* N : LidSideRight +* n : LidRight +* Oo : ChestHinge +* Pp : ChestHingeTop +* Q : ChestHingeFront +* q : ChestHingePin +* R : RackEdge +* s : StackableEdge +* S : StackableEdgeTop +* š : StackableFeet +* Š : StackableHoleEdgeTop +* T : RoundedTriangleFingerHolesEdge +* t : RoundedTriangleEdge +* uUvV : CabinetHingeEdge +* X : FlexEdge +* y : HandleEdge +* Y : HandleHoleEdge +* Z : GroovedEdgeCounterPart +* z : GroovedEdge + +Edge base class +--------------- + +.. autoclass:: boxes.edges.BaseEdge + :members: + +.. automethod:: boxes.edges.BaseEdge.__call__ + +Settings Class +-------------- + +.. autoclass:: boxes.edges.Settings + :members: + + +Straight Edges +-------------- + +.. autoclass:: boxes.edges.Edge +.. autoclass:: boxes.edges.OutSetEdge + +Grip +---- + +.. autoclass:: boxes.edges.GripSettings +.. autoclass:: boxes.edges.GrippingEdge + +Stackable Edges +--------------- + +.. autoclass:: boxes.edges.StackableEdge +.. autoclass:: boxes.edges.StackableEdgeTop + +Stackable Edge Settings +....................... + +.. autoclass:: boxes.edges.StackableSettings + :members: + +Finger joints +------------- + +Finger joints are a simple way of joining two sheets (e.g. of plywood). They +work best at an 90° angle. There are two different sides matching each +other. As a third alternative there are holes that the fingers of one +sheet can plug into. This allows stable T connections especially +useful for inner walls. + +.. autoclass:: boxes.edges.FingerJointEdge +.. autoclass:: boxes.edges.FingerJointEdgeCounterPart +.. autoclass:: boxes.edges.FingerHoleEdge +.. autoclass:: boxes.edges.CrossingFingerHoleEdge + +In addition there is + +.. autoclass:: boxes.edges.FingerHoles + +which is no Edge but fits ``FingerJointEdge``. + +An instance of is accessible as **Boxes.fingerHolesAt**. + +Finger Joint Settings +..................... + +.. autoclass:: boxes.edges.FingerJointSettings + :members: + +Bed Bolts +......... + +.. autoclass:: boxes.edges.BoltPolicy + +.. autoclass:: boxes.edges.Bolts + +Dove Tail Joints +---------------- +Dovetails joints can only be used to join two pieces flatly. This +limits their use to closing some round form created with flex areas or +for joining several parts to a bigger one. For this use case they are +much stronger than simple finger joints and can also bare pulling forces. + +.. autoclass:: boxes.edges.DoveTailJoint +.. autoclass:: boxes.edges.DoveTailJointCounterPart + +Dove Tail Settings +.................. + +.. autoclass:: boxes.edges.DoveTailSettings + :members: + +Flex +---- +.. autoclass:: boxes.edges.FlexEdge + +Flex Settings +............. + +.. autoclass:: boxes.edges.FlexSettings + +Slots +----- +.. autoclass:: boxes.edges.Slot +.. autoclass:: boxes.edges.SlottedEdge + +CompoundEdge +------------ +.. autoclass:: boxes.edges.CompoundEdge + +Hinges +------ + +Hinge Settings +.............. + +.. autoclass:: boxes.edges.HingeSettings + +Hinge +..... + +.. autoclass:: boxes.edges.Hinge + +HingePin +........ + +.. autoclass:: boxes.edges.HingePin diff --git a/html/_sources/api_examples.rst.txt b/html/_sources/api_examples.rst.txt new file mode 100644 index 0000000..ffb1d4f --- /dev/null +++ b/html/_sources/api_examples.rst.txt @@ -0,0 +1,97 @@ +Examples +-------- + +Decide whether you want to start from scratch or want to rework an +existing generator. + +You should go over the arguments first. Get at least the most basic +arguments done. For things you are still unsure you can just use a +attribute set in the .__init__() method and turn it into a proper +argument later on. + +Depending on what you want to do you can work on the different levels +of the API. You can either use what is there and combine it into +something new or you can implements new things in the appropriate level. + +Here are some examples: + +Housing for some electronics +............................ + +You can use the ElectronicsBox or the ClosedBox as a basis. Write some +callbacks to place holes in the walls to allow accessing the ports of +the electronics boards. Place some holes to screw spacers into the +bottom to mount the PBC on. + +NemaMount +......... + +This is a good non box example to look at. + +.. autoclass:: boxes.generators.nemamount.NemaMount + +Note that although it produces a cube like object it uses separate +variables (``x``, ``y``, ``h``) for the different axis. Probably +because it started as a copy of another generator like ``ClosedBox``. + +DisplayShelf +............ + +.. autoclass:: boxes.generators.displayshelf.DisplayShelf + +The DisplayShelf is completely made out of rectangularWalls(). It uses +a callback to place all the fingerHolesAt() right places on the sides. +While the use of the Boxes.py API is pretty straight forward the +calculations needed are a bit more tricky. You can use the ``debug`` +default param to check if you got things right when attempting +something like this yourself. + +Note that the front walls and the shelfs form a 90° angle so they work +with the default FingerJoints. + +BinTray +....... + +.. autoclass:: boxes.generators.bintray.BinTray + +The BinTray is based on the TypeTray generator: + +.. autoclass:: boxes.generators.typetray.TypeTray + +TypeTray is an already pretty complicated generator. + +BinTray replaces the now vertical front (former top) edges with a +special purpose one that does add the triangles: + +.. autoclass:: boxes.generators.bintray.BinFrontEdge + +The ``hi`` (height of inner walls) argument was removed although the +variable is still used internally - out of laziness. + +To complete the bin the front walls are added. Follow up patches then +switched the slots between the vertical and horizontal walls to have +better support for the now bottoms of the bins. Another patch adds +angled finger joints for connecting the front walls with the bottoms +of the bins. + +The TrafficLight generator uses a similar technique implementing its +own Edge class. But it uses its own code to generate all the wall needed. + +Stachel +....... + +.. autoclass:: boxes.generators.stachel.Stachel + +Stachel allows mounting a monopod to a bass recorder. It is basically +just one part repeated with different parameters. It can't really make +use of much of the Boxes.py library. It implements this one part +including the ``move`` parameter and draws everything using the +``.polyline()`` method. This is pretty painful as lots of angles and +distances need to be calculated by hand. + +For symmetric sections it passes the parameters to ``.polyline`` twice +-- first in normal order and then reversed to get the mirrored section. + +This generator is beyond what Boxes.py is designed for. If you need +something similar you may want to use another tool like OpenScad or a +traditional CAD program. diff --git a/html/_sources/api_existing_parts.rst.txt b/html/_sources/api_existing_parts.rst.txt new file mode 100644 index 0000000..df53869 --- /dev/null +++ b/html/_sources/api_existing_parts.rst.txt @@ -0,0 +1,27 @@ +Existing Parts +-------------- + +A couple of commands can create whole parts like walls. Typically the +sizes given are the inner dimension not including additional space +needed for burn compensation or joints. + +Currently there are the following parts: + +.. automethod:: boxes.Boxes.rectangularWall +.. automethod:: boxes.Boxes.flangedWall +.. automethod:: boxes.Boxes.rectangularTriangle +.. automethod:: boxes.Boxes.regularPolygonWall +.. automethod:: boxes.Boxes.polygonWall +.. automethod:: boxes.Boxes.roundedPlate +.. automethod:: boxes.Boxes.surroundingWall + +Parts Class +........... + +More parts are available in a separete class. An instance is available as +**Boxes.parts** + +.. automethod:: boxes.parts.Parts.disc +.. automethod:: boxes.parts.Parts.waivyKnob +.. automethod:: boxes.parts.Parts.concaveKnob +.. automethod:: boxes.parts.Parts.ringSegment diff --git a/html/_sources/api_generator.rst.txt b/html/_sources/api_generator.rst.txt new file mode 100644 index 0000000..bf35893 --- /dev/null +++ b/html/_sources/api_generator.rst.txt @@ -0,0 +1,45 @@ + +Generators +========== + +Generators are sub classes of + +.. autoclass:: boxes.Boxes + +Most code is directly in this class. Sub class are supposed to over +write the ``.__init__()`` and ``.render()`` method. + +The Boxes class keeps a canvas object (self.ctx) that all +drawing is made on. In addition it keeps a couple of global settings +used for various drawing operations. See the ``.__init__()`` method +for the details. + +For implementing a new generator forking an existing one or using the +``boxes/generators/_template.py`` is probably easier than starting +from scratch. + +Many methods and attributes are for use of the sub classes. These +methods are the interface for the user interfaces to interact with the +generators: + +.. automethod:: boxes.Boxes.__init__ + +.. automethod:: boxes.Boxes.parseArgs +.. automethod:: boxes.Boxes.render + +.. automethod:: boxes.Boxes.open +.. automethod:: boxes.Boxes.close + +Handling Generators +------------------- + +To handle the generators there is code in the ``boxes.generators`` +package. + +.. automodule:: boxes.generators + :members: + :undoc-members: + +This adds generators to the user interfaces automatically. For this to +work it is important that the class names are unique. So whenever you +start a new generator please change the class name right away. diff --git a/html/_sources/api_navigation.rst.txt b/html/_sources/api_navigation.rst.txt new file mode 100644 index 0000000..7845a10 --- /dev/null +++ b/html/_sources/api_navigation.rst.txt @@ -0,0 +1,42 @@ +Navigation +---------- + +The back end can both move the origin and the current point from +which the next line is going to start. Boxes.py hides this by using +Turtle Graphics commands that also move the origin to the end of the +last line. Other drawing commands restore the current position after +they are finished. + +Moving the origin like this allows ignoring the absolute coordinates +and do all movement and drawing to be relative to the current +position. The current positions does not only consist of a point on +the drawing canvas but also a direction. + +To move the origin to a different location there are these to methods: + +.. automethod:: boxes.Boxes.moveTo +.. automethod:: boxes.Boxes.moveArc + +Often it is necessary to return to a position e.g. after placing a +row of parts. This can be done with the following context manager: + +.. automethod:: boxes.Boxes.saved_context() + +It can be used with the following code pattern: + +.. code-block:: python + + with self.saved_context(): + self.rectangularWall(x, h, move="right") + self.rectangularWall(y, h, move="right") + self.rectangularWall(y, h, move="right") + self.rectangularWall(x, h, move="right") + self.rectangularWall(x, h, move="up only") + + # continue above the row + +Parts of the code still directly use the back end primitives **Boxes.ctx.save()** +and **Boxes.ctx.restore()**. But this has several disadvantages and is +discouraged. For one it requires matchiung calls. It also does not +reset the starting point of the next line. This is "healed" by a +follow up **.moveTo()**. Use **.moveTo(0, 0)** if in doubt. diff --git a/html/_sources/api_parts.rst.txt b/html/_sources/api_parts.rst.txt new file mode 100644 index 0000000..5d13215 --- /dev/null +++ b/html/_sources/api_parts.rst.txt @@ -0,0 +1,86 @@ +Parts +----- + + + + +There are a few parameter shared by many of those parts: + +The callback parameter +...................... + +The callback parameter can take on of the following forms: + +* A function (or bound method) that expects one parameter: the number of the side the callback is currently called for. +* A dict with some of the numbers of the sides as keys and functions without parameters as values. +* A list of functions without parameters. The list may contain None as place holder and be shorter than the number of sides. + +The callback functions are called with the side of the part at the +positive x and y axis. If the edge uses up space this space is below +the x axis. You do not have to restore the coordinate settings in the +callback. + +Instead of functions it can be handy to use a lambda expression +calling the one building block function you need (e.g. fingerHolesAt). + +For your own parts you can use this helper function: + +.. automethod:: boxes.Boxes.cc + +For finding the right piece to the *callback* parameter this function is used: + +.. automethod:: boxes.Boxes.getEntry + + +The move parameter +.................. + +For placing the parts the ``move`` parameter can be used. It is string +with space separated words - at most one of each of those options: + +* left / right +* up / down +* only + +If "only" is given the part is not drawn but only the move is +done. This can be useful to go in one direction after having placed +multiple parts in the other and have returned with ``.ctx.restore()``. + +For implementing parts the following helper function can be used to +implement a ``move`` parameter: + +.. automethod:: boxes.Boxes.move + +It needs to be called before and after drawing the actual part with +the proper ``before`` parameter set. + +The edges parameter +................... + +The ``edges`` parameter needs to be an iterable of Edge instances to be +used as edges of the part. Instead of instances it is possible to pass +a single character that is looked up in the ``.edges`` dict. This +allows to pass a string with the desired characters per edge. By +default the following character are supported: + +* e : straight edge +* E : as above but extended outside by one thickness +* f, F : finger joints +* h : edge with holes for finger joints +* d, D : dove tail joints + +Generators can register their own Edges by putting them into the +``.edges`` dictionary. + +Same applies to the parameters of ``.surroundingWall`` although they +denominate single edge (types) only. + +PartsMatrix +........... + +To place many of the same part partMatrix can used: + +.. automethod:: boxes.Boxes.partsMatrix + +It creates one big block of parts. The move param treat this block like on big +part. diff --git a/html/_sources/apidoc.rst.txt b/html/_sources/apidoc.rst.txt new file mode 100644 index 0000000..cd3b80f --- /dev/null +++ b/html/_sources/apidoc.rst.txt @@ -0,0 +1,20 @@ +Using the Boxes.py API +====================== + +If there is no generator fitting your needs you can either adjust an +existing one (may be by copying it to another name first) or writing a +new one from scratch. + +.. toctree:: + :maxdepth: 1 + + api_architecture + api_generator + api_arguments + api_navigation + api_parts + api_existing_parts + api_edges + api_drawing + api_burn + api_examples diff --git a/html/_sources/boxes.rst.txt b/html/_sources/boxes.rst.txt new file mode 100644 index 0000000..3778556 --- /dev/null +++ b/html/_sources/boxes.rst.txt @@ -0,0 +1,120 @@ +boxes package +============= + +Subpackage boxes.generators +--------------------------- + +.. automodule:: boxes.generators + :members: + :undoc-members: + :show-inheritance: + +:doc:`generators` + +Submodules +---------- + +boxes.Color module +------------------ + +.. automodule:: boxes.Color + :members: + :undoc-members: + :show-inheritance: + +boxes.edges module +------------------ + +.. automodule:: boxes.edges + :members: + :undoc-members: + :show-inheritance: + +boxes.formats module +-------------------- + +.. automodule:: boxes.formats + :members: + :undoc-members: + :show-inheritance: + +boxes.gears module +------------------ + +.. automodule:: boxes.gears + :members: + :undoc-members: + :show-inheritance: + +boxes.lids module +----------------- + +.. automodule:: boxes.lids + :members: + :undoc-members: + :show-inheritance: + +boxes.mounts module +------------------- + +.. automodule:: boxes.mounts + :members: + :undoc-members: + :show-inheritance: + +boxes.parts module +------------------ + +.. automodule:: boxes.parts + :members: + :undoc-members: + :show-inheritance: + +boxes.pulley module +------------------- + +.. automodule:: boxes.pulley + :members: + :undoc-members: + :show-inheritance: + +boxes.robot module +------------------ + +.. automodule:: boxes.robot + :members: + :undoc-members: + :show-inheritance: + +boxes.servos module +------------------- + +.. automodule:: boxes.servos + :members: + :undoc-members: + :show-inheritance: + +boxes.svgutil module +-------------------- + +.. automodule:: boxes.svgutil + :members: + :undoc-members: + :show-inheritance: + +boxes.vectors module +-------------------- + +.. automodule:: boxes.vectors + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: boxes + :members: + :undoc-members: + :show-inheritance: diff --git a/html/_sources/faq.rst.txt b/html/_sources/faq.rst.txt new file mode 100644 index 0000000..f41b851 --- /dev/null +++ b/html/_sources/faq.rst.txt @@ -0,0 +1,54 @@ +========================== +Frequently Asked Questions +========================== + +.. toctree:: + :maxdepth: 1 + +Can I sell boxes I created with Boxes.py +---------------------------------------- + +Yes. Boxes.py is under the GPLv3 license (see https://www.gnu.org/licenses/gpl-3.0.html). This license grants you far reaching rights on what you can do with the software including using it and the drawings it produces to any means. The license also puts some obligations on you. But those are about changing and distributing the software itself. The resulting drawings do not fall under the GPL license. + +Why do my parts not fit together? +--------------------------------- + +Well, this could be a bug in Boxes.py but there are a few more likely causes to check for: + +* The material you use does not have the thickness you think. Measure it with at least a caliper. Even a few hundredth of a milimeter will make the difference between a loose fit, a light or a heavy pressfit or no fit at all. + +* You might have choosen the "burn" value too big. As it compensates for the material cut away by the laser smaller values make a looser fit, bigger values make a tighter fit. The right value may be different for different materials and different thicknesses. + +Why is my box a bit too big? +---------------------------- + +By default all sizes are inner sizes. So on the outside the box is bigger as the walls need to go somewhere. Some generators offer an "outside" param that includes the walls in the measurements. In general you should check the generated parts for plausability before hitting the start button on your laser cutter. + +Why is my box a bit too small? +------------------------------ + +See above. + +Why are my parts in the totally wrong size? +------------------------------------------- + +Unfortunately some formats do not save the units of measurement or don't do so properly. DXF and SVG fall into this category. So different tools may see the same file in different sizes. You can use the "reference" param to get a rectangle of a defined size to check if the size is still right at the end of your tool chain. + +Why are there tiny, weird loops in the corners? +----------------------------------------------- + +These are called dog bones and make sure the corner is completely cut out. As lasers and milling tools are round they can't cut sharp inner corners. Have a look at :doc:`burn correction details ` for details. + +I really don't want those weird, tiny loops? +-------------------------------------------- + +You can set the ``inner_corners`` default setting to ``corner`` + +What settings were used to generate a drawing? +---------------------------------------------- + +If you do have a SVG or PostScript you can look into the meta data of the file. Most document viewers will have a ``Document properties`` window. You can also just open the file with a text editor and find the details at the first few lines. + +Note that you can just use the URL in there to get back to the settings page to change some values. The difference between the settings and the rendered drawing is just ``render=0`` or ``render=1`` at the end of the URL. + +For other formats you are currently out of luck. diff --git a/html/_sources/generators.rst.txt b/html/_sources/generators.rst.txt new file mode 100644 index 0000000..c6e1348 --- /dev/null +++ b/html/_sources/generators.rst.txt @@ -0,0 +1,9 @@ +All Box Generators +================== + +Generators are organized in several Groups + +.. contents:: + :local: + +.. include:: generators.inc diff --git a/html/_sources/index.rst.txt b/html/_sources/index.rst.txt new file mode 100644 index 0000000..7a40427 --- /dev/null +++ b/html/_sources/index.rst.txt @@ -0,0 +1,30 @@ +.. boxes.py documentation master file, created by + sphinx-quickstart on Sun Mar 27 12:04:59 2016. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Boxes.py +======== + +Create boxes and more with a laser cutter! + +Contents: + +.. toctree:: + :maxdepth: 1 + + README + faq + install + usermanual + CONTRIBUTING.rst + apidoc + generators + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/html/_sources/install.rst.txt b/html/_sources/install.rst.txt new file mode 100644 index 0000000..8e24196 --- /dev/null +++ b/html/_sources/install.rst.txt @@ -0,0 +1,114 @@ +Installation +============ + +Boxes.py is a pure Python project that does support the regular setuptools +method of shipping with :code:`setup.py`. :code:`setup.py --help-commands` and +:code:`setup.py CMD --help` provide the necessary documentation for building, +installing or building binary formats. + +Requirements +------------ + +Affine +........ +:code:`Affine` (package name may be :code:`python-affine` or +:code:`python3-affine`) is used for vector calculation. + +Shapely +....... +:code:`shapely` (package name may be :code:`python-shapely` or +:code:`python3-shapely`) is used for filling shapes (with holes). + + +Markdown +........ +:code:`Markdown` (package name may be :code:`python-markdown` or +:code:`python3-markdown`) is used to format the description texts. + + +setuptools +.......... + +Setup.py uses the :code:`setuptools` library (package name may be +:code:`python*-setuptools`). You only need it if you want to build the +package. + +ps2edit +....... + +While not a hard requirement Boxes.py uses :code:`ps2edit` to offer formats +that are not supported by Cairo: DXF, gcode, PLT. Currently the location +Boxes.py looks for :code:`ps2edit` is hard coded to :code:`/usr/bin/pstoedit` +in the :code:`boxes.formats.Formats` class. + +Python +...... + +Boxes.py is implemented in Python 3. It used to work on Python 2.7, +too. But with the Python 2 approaching end of life support has been dropped. + +Sphinx +...... + +For building the documentation locally you need the *Sphinx* documentation +generator (package name may be python-sphinx or python3-sphinx). It is +not needed for anything else. Boxes.py can be run and changed just +fine without. + +Running from working dir +------------------------ + +Due to lazy developer(s) Boxes.py can also run from the Git checkout. +The scripts in :code:`scripts/` are all suppossed to just work right +after :code:`git clone`. The Inkscape needs a bit manual work to get +running. See below. + +Inkscape +-------- + +**As binary** + +Boxes.py can be used as a set of Inkscape plugins. The package does +install the necessary .inx files to :code:`/usr/share/inkscape/extensions` +on unix operating systems. The .inx files assume that the :code:`boxes` +executable is available in the path (which it is when installing the +binary package) + +**git repository easy way** + +After cloning it may be most convenient to generate the .inx files +right in place by executing :code:`scripts/boxes2inkscape` with the taget +path as only parameter. + +- global: :code:`scripts/boxes2inkscape /usr/share/inkscape/extensions/` +- userspace: :code:`scripts/boxes2inkscape ~/.config/inkscape/extensions/` + +On non unix operating the target directories may differ. You can look +up the directories *"User extensions"* and *"Inkscape extensions"* within +the Inkscape preferences *Edit -> Preferences... -> System*. + +**git repository manual way** + +:code:`setup.py build` creates the :code:`*.inx` files in the :code:`inkex/` directory. + +They then have to be copied in either the global or the per user +extension directory of Inkscape. These are +:code:`/usr/share/inkscape/extensions/` and +:code:`~/.config/inkscape/extensions/` on a unix operating system. +On non unix operating the target directories may differ. You can look +up the directories *"User extensions"* and *"Inkscape extensions"* within +the Inkscape preferences *Edit -> Preferences... -> System*. + +As an alternative you can create a symlink to the :code:`inkex/` directory +within the desired inkscape extension directory. + + +Platform specific instructions +------------------------------ + +.. toctree:: + :maxdepth: 2 + :glob: + + install/* + diff --git a/html/_sources/install/macos.rst.txt b/html/_sources/install/macos.rst.txt new file mode 100644 index 0000000..f385d15 --- /dev/null +++ b/html/_sources/install/macos.rst.txt @@ -0,0 +1,124 @@ +macOS +===== + +It is recommended to use Homebrew to install the dependencies for Boxes.py. +See `brew.sh `__ on how to install Homebrew. + +General +------- + +1. Install Python 3 and other dependencies: + + .. code:: + + brew install python3 git + + Optional: + + .. code:: + + brew install pstoedit + + +2. Install cairio: + + .. code:: + + brew install pkg-config + + +3. Install required Python modules: + + .. code:: + + pip3 install Markdown affine shapely + +4. Download Boxes.py via Git: + + .. code:: + + git clone https://github.com/florianfesti/boxes.git + +5. Run Boxes.py: + + Local web server on port 8000: + + .. code:: + + ./scripts/boxesserver + + Command line variant (CLI): + + .. code:: + + ./scripts/boxes + + +System-wide with Inkscape extension +----------------------------------- + +To install Boxes.py system-wide with the Inkscape extension, following steps +are required: + +1. Install Inkscape with Homebrew Cask + (requires `XQuartz `__): + + .. code:: + + brew install inkscape + +2. From the root directory of the repository, run: + + .. code:: + + ./setup.py install + +3. Now :code:`boxes` and :code:`boxesserver` can be executed like other commands + and the Inkscape extension should be available. + + +Troubleshooting +............... + +When using the Inkscape extension something like the following error +might occur: + +:: + + Traceback (most recent call last): + File "/Users/martin/.config/inkscape/extensions/boxes", line 107, in + main() + File "/Users/martin/.config/inkscape/extensions/boxes", line 47, in main + run_generator(name, sys.argv[2:]) + File "/Users/martin/.config/inkscape/extensions/boxes", line 73, in run_generator + box.close() + File "/usr/local/lib/python3.7/site-packages/boxes-0.1-py3.7.egg/boxes/__init__.py", line 594, in close + svgutil.svgMerge(self.output, self.inkscapefile, out) + File "/usr/local/lib/python3.7/site-packages/boxes-0.1-py3.7.egg/boxes/svgutil.py", line 144, in svgMerge + from lxml import etree as et + ImportError: dlopen(/Applications/Inkscape.app/Contents/Resources/lib/python2.7/site-packages/lxml/etree.so, 2): Symbol not found: _PyBaseString_Type + Referenced from: /Applications/Inkscape.app/Contents/Resources/lib/python2.7/site-packages/lxml/etree.so + Expected in: flat namespace + +This is because Inkscape on macOS ships its own version of Python 2.7 where +:code:`lxml` and other dependencies are missing. + +A workaround is to edit the file at +:code:`/Applications/Inkscape.app/Contents/Resources/bin/inkscape`. +At line 79 there should be following code: + +.. code:: + + export PYTHONPATH="$TOP/lib/python$PYTHON_VERS/site-packages/" + +which needs to be changed to + +.. code:: + + #export PYTHONPATH="$TOP/lib/python$PYTHON_VERS/site-packages/" + +This forces Inkscape to use the Python version installed by Homebrew which +has all the necessary dependencies installed. + +Note: This might break other extensions. In this case simply change the line +back and restart Inkscape. diff --git a/html/_sources/install/windows.rst.txt b/html/_sources/install/windows.rst.txt new file mode 100644 index 0000000..836b9e0 --- /dev/null +++ b/html/_sources/install/windows.rst.txt @@ -0,0 +1,91 @@ +Windows +======= + +Getting the Inkscape plugins to run will likely need manual +installation (see above). Note that Inkscape may come with its own +Python. If you run into trouble or have better installation +instructions please open a ticket on GitHub. + +Native +------ + +Following steps are known to work under Windows 10 (64-bit): + +1. Go to https://www.python.org/downloads/windows/ + and download the "Windows x86-64 executable installer" for Python 3.7 + + .. figure:: windows_browser_download_python.png + :scale: 50% + :alt: Screenshot of python.org with download of Python 3.7 (64-bit) + :align: center + +2. Install Python 3.7 and make sure to check "Add Python 3.7 to PATH" + while doing so + + .. figure:: windows_install_python_path.png + :scale: 50% + :alt: Screenshot of Python 3.7 (64-bit) installer with PATH checked + :align: center + +3. Run the command :code:`pip install Markdown affine shapely` + (Note: If the command pip is not found, you probably forgot to add the + Python installation to the PATH environment variable in step 2) + +4. Download Boxes.py as ZIP archive from GitHub + + .. figure:: windows_browser_download_boxespy.png + :scale: 50% + :alt: Screenshot of download from Boxes.py project on GitHub + :align: center + +5. Extract the ZIP archive + (e.g. via the built-in Windows feature or other tools like 7-Zip) + + .. figure:: windows_boxespy_zip_extract.png + :scale: 50% + :alt: Screenshot of Windows tools to extract the ZIP archive + :align: center + +6. Change into the folder for Boxes.py, + e.g. with the command :code:`cd \Users\[USERNAME]\Downloads\boxes-master` +7. Run the development server with the command + :code:`python scripts\boxesserver` + Note: You likely will be notified by your firewall that it blocked network + access. If you want to use boxesserver you need to allow connections. + + .. figure:: windows_cmd_python_boxesserver_firewall.png + :scale: 50% + :alt: Screenshot of command for running boxesserver and firewall notice + :align: center + +8. Open the address http://localhost:8000/ in your browser and have fun :) + + .. figure:: windows_browser_boxespy.png + :scale: 50% + :alt: Screenshot of a browser window running Boxes.py locally + :align: center + + +Additionally the command line version of Boxes.py can be used with +the command :code:`python scripts\boxes`. + +Windows Subsystem for Linux +--------------------------- + +Another way of installing Boxes.py on Windows is to use the Windows Subsystem +for Linux (WSL). This requires newer versions of Windows 10. Once it is +installed (e.g. via the Ubuntu App from the Microsoft Store), the installation +is identical to the installation on Linux systems. + +Once wsl is installed, run it and enter the following commands: + +- :code:`cd ~` +- :code:`git clone https://github.com/florianfesti/boxes.git` +- :code:`cd ~/boxes` +- :code:`python3 -m pip install -r ~/boxes/requirements.txt` +- :code:`python3 ~/boxes/scripts/boxesserver` + +.. figure:: win11-wsl-boxesserver-localhost.png + :scale: 50% + :alt: Screenshot of a browser window running Boxes.py locally on WSL + :align: center diff --git a/html/_sources/modules.rst.txt b/html/_sources/modules.rst.txt new file mode 100644 index 0000000..8a714cb --- /dev/null +++ b/html/_sources/modules.rst.txt @@ -0,0 +1,9 @@ +:orphan: + +boxes +===== + +.. toctree:: + :maxdepth: 4 + + boxes diff --git a/html/_sources/usermanual.rst.txt b/html/_sources/usermanual.rst.txt new file mode 100644 index 0000000..4f442ec --- /dev/null +++ b/html/_sources/usermanual.rst.txt @@ -0,0 +1,248 @@ +============== +Using Boxes.py +============== + +.. toctree:: + :maxdepth: 2 + +Boxes.py is made of a library that is not visible to the user and +multiple generators -- each having its own set of parameters and +creating a drawing for it own type of object. These generators are +divided up into different groups to make it easier to find them: + +* Boxes +* Boxes with flex +* Trays and Drawer Inserts +* Shelves +* Parts and Samples +* Misc +* Unstable + +The parameters for each generators also come in groups. + +Units of measurements +--------------------- + +In general all measurements are in Millimeters (mm). There is no +option to change the units of measurement and there is no plan to add +such a option. + +A second way to define lengths is as multiple of the material +thickness which is one of the standard parameters described +below. This allows features to retain their proportions even if some +parts depend on the material thickness. + +The description texts should state the unit of each argument - +please open a ticket if the units are missing somewhere. + +.. _default-args: + +Default arguments +----------------- +In the web interface this is the bottom group right before the +``Render`` button. These are basically all technical settings that +have little to do with the object being rendered but more with the +material used and the way the drawing and the material is processed. + +The settings are + +thickness +......... + +The thickness of the material used. This value is used at many places +to define the sizes of features like finger joints, hinges, ... It is +very important to get the value right - especially if there are +fingers that need to fit into some holes. Be aware that many materials +may differ from their nominal value. You should **always measure the +thickness** for every sheet unless you have a very reliable supply +that is known to stick very closely to specifications. For (ply) wood +even a 100th of a millimeter makes a notable difference in how stiff +the fit is. Harder more brittle materials may be even more picky. + +burn +.... + +The burn correction aka kerf is the distance the laser has to keep +from the edge of the parts. If the laser would cut right on the edge +it would cut away the outside perimeter of the part. So the burn value is +basically the radius of the laser - or half the width of the laser cut. + +The value of the burn parameter depends on your laser cutter, the +material cut and the thickness of the material. In addition it depends +on whether you want the parts to be over or under sized. Materials +that are spongy like wood can be cut oversized (larger burn value) so +they can be press fitted with some force and may be assembled without +glue. Brittle materials (like Acrylic) need to be cut undersized to +leave a gap for the glue. + +**Note:** The way the burn param works is a bit counter intuitive. Bigger +burn values make a tighter fit. Smaller values make a looser fit. + +Small changes in the burn param can make a notable difference. Typical +steps for adjustment are 0.01 or even 0.005mm to choose between +different amounts of force needed to press plywood together. + +To find the right burn value cut out a rectangle and then measure how +much smaller it is than its nominal size. The burn value should be +around half of the difference. To test the fit for several values at +once you can use the **BurnTest** generator in the "Parts and Samples" section. + +format +...... + +Boxes.py is able to create multiple formats. For most of them it +requires ``ps2edit``. Without ``ps2edit`` only ``SVG`` +and ``postscript`` (ps) is supported. Otherwise you can also +select + +* ai +* dxf +* gcode +* pdf +* plt + +Other formats supported by ``ps2edit`` can be added easily. Please +open a ticket on GitHub if you need one. + +tabs +.... + +Tabs are small bridges between the parts and surrounding material that +keep the part from falling out. In theory their width should be +affected by the burn parameter. But it is more practical to have both +independent so you can tune them separately. Most parts and generators +support this features but there may be some that don't. + +For plywood values of 0.2 to 0.3mm still allow getting the parts out +by hand (Depending on you laser cutter and the exact material). With +little more you will need a knife to cut them loose. + +inner_corners +............. + +How to handle inner corners. Inner corners are an issue as a round +tool like a laser or mill cannot create sharp inner corners. There are +different options: + +* ``loop`` create a loop that fills the corner +* ``corner`` just a simple sharp corner in the path that will leave a + radius untouched. +* ``backarc`` naive implementation with inverted arcs connection the + straight lines. + +See also :doc:`burn correction details ` + +debug +..... + +Most regular users won't need this option. + +It adds some construction lines that are helpful for +developing new generators. Only few pieces actually support the +parameter. The most notable being finger holes that show the border of +the piece they belong to. This helps checking whether the finger holes +are placed correctly. + +reference +......... + +Converting vector graphics is error prone. Many formats have very +weird ideas how their internal units translates to real world +dimensions. If reference is set to non zero Boxes.py renders a rectangle of +the given length. It can be used to check if the drawing is still at +the right scale or may give clues on how to scale it back to the right +proportions. + +Common Parameters and Types +--------------------------- + +Section parameters +.................. + +Some generators support an arbitrary number of sections. This can be used for rows or columns of compartments, staggered heights or otherwise dividing some length in multiple sub sections. The standard parameter making use of this are ``sx``, ``sy`` and ``sh`` (instead of ``x``, ``y`` and ``h``). + +Most generators will add walls between the comparments, so the total size might be larger depending on the number of compartments (and additional walls). + +The sizes of the sections are divided by a colon (``:``) e.g. ``30:25.5:70``. Instead of repeating the same value they can be replaced by ``value*numberofsections`` e.g. ``50*3`` meaning the same as ``50:50:50``. To equally divide a length into several sections ``overallwidth/numberofsections`` can be used - e.g. ``120/4`` being the same as ``30:30:30:30``. All these formats can be freely mixed. + +mounting_holes +.................. +Some generators provide the option to create pear shaped mounting holes. To generate the right size holes, the shaft and the head diameter of the mounting screw must be configured. The format is "shaft:head", both diameters given in mm (e.g ``3.5:6.5``). If only the shaft diameter is given (e.g. ``3.5``), a round mounting hole is generated. Setting the mounting hole diameter parameter to ``0`` disables the creation of mounting holes. + +outside +....... + +Most measurements are internal sizes. If a generator offers this parameter it will re-calculate the inner sizes to fit walls and outside features within the given dimensions. This can be a bit surprising for edge types that have protrusions like hinge eyes, handles, feet, etc as those are typically also taken into account. If the dimensions are not sufficient to accommodate these features the box may not work properly. Most generators do not have checks for such issues (like negative height) and it is left in the responsibility of the user to check if the result still is sane. + +For generators offering multiple compartments this will also fit-in the inner walls. It will sum up all sections then subtract the space needed for the walls and then scale all compartments so they will fill the remaining space. + + +Edge Type parameters +-------------------- + +All but the simplest edge types have a number of settings controlling +how exactly they should look. Generators are encouraged to offer these +settings to the user. In the web interface they are folded up. In the +command line interface they are grouped together. Users should be +aware that not all settings are practical to change. For now Boxes.py +does not allow hiding some settings. + +Finger Joint Settings +..................... + +.. glossary:: + + finger + width of the fingers in multiples of the thickness + + space + width of the spaces between fingers in multiples of the thickness + + surroundingspaces + amount of space before the first and after the last finger. This is in multiples of regular space between fingers. The actual space is larger when needed but can be smaller for very short edges. + + style + how finger joints should look like. There may be more styles to choose from in the future. Note that snap fingers will only be drawn for fingers of width 1.9 and above. + + extra_length + Make the outset part of the finger joint longer to allow grinding off burn marks. Note that this may not be great for non 90° joints where the corner is butted against the opposing cutout. + +Stackable Edge Settings +....................... + +For boxes to actually stack they need to be the same width and depth and ``angle``, ``width`` and ``height`` of the feet need to be the same. + +.. glossary:: + + angle + inside angle of the feet. + + height + height of the feet + + holedistance + distance from finger holes to bottom edge. May be reduced to save height by sacrificing stability of the connection to the bottom of the box. + + width + width of the feet + +Colors +------ +The generated files uses the following color conventions: + +.. glossary:: + + Black + The outer edges of a part + + Blue + Inner edges of a part + + Red + Comments or help lines that are not meant to be cut or etched + + Green + Etchings + +Normally you will cut things in the order: Green, Blue, Black. If other +colors are present, the meaning should hopefully be obvious. diff --git a/html/_static/_sphinx_javascript_frameworks_compat.js b/html/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 0000000..8549469 --- /dev/null +++ b/html/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,134 @@ +/* + * _sphinx_javascript_frameworks_compat.js + * ~~~~~~~~~~ + * + * Compatability shim for jQuery and underscores.js. + * + * WILL BE REMOVED IN Sphinx 6.0 + * xref RemovedInSphinx60Warning + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/html/_static/basic.css b/html/_static/basic.css new file mode 100644 index 0000000..4e9a9f1 --- /dev/null +++ b/html/_static/basic.css @@ -0,0 +1,900 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/html/_static/boxes-logo.svg b/html/_static/boxes-logo.svg new file mode 100644 index 0000000..dd5dd8a --- /dev/null +++ b/html/_static/boxes-logo.svg @@ -0,0 +1,340 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/html/_static/doctools.js b/html/_static/doctools.js new file mode 100644 index 0000000..527b876 --- /dev/null +++ b/html/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/html/_static/documentation_options.js b/html/_static/documentation_options.js new file mode 100644 index 0000000..cf359c0 --- /dev/null +++ b/html/_static/documentation_options.js @@ -0,0 +1,14 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '0.1', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/html/_static/favicon.ico b/html/_static/favicon.ico new file mode 100644 index 0000000..2e76dba Binary files /dev/null and b/html/_static/favicon.ico differ diff --git a/html/_static/file.png b/html/_static/file.png new file mode 100644 index 0000000..a858a41 Binary files /dev/null and b/html/_static/file.png differ diff --git a/html/_static/jquery-3.6.0.js b/html/_static/jquery-3.6.0.js new file mode 100644 index 0000000..fc6c299 --- /dev/null +++ b/html/_static/jquery-3.6.0.js @@ -0,0 +1,10881 @@ +/*! + * jQuery JavaScript Library v3.6.0 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright OpenJS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2021-03-02T17:08Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + // Support: QtWeb <=3.8.5, WebKit <=534.34, wkhtmltopdf tool <=0.12.5 + // Plus for old WebKit, typeof returns "function" for HTML collections + // (e.g., `typeof document.getElementsByTagName("div") === "function"`). (gh-4756) + return typeof obj === "function" && typeof obj.nodeType !== "number" && + typeof obj.item !== "function"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +var document = window.document; + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.6.0", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return flat( ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), + function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); + } ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.6 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2021-02-16 + */ +( function( window ) { +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ( {} ).hasOwnProperty, + arr = [], + pop = arr.pop, + pushNative = arr.push, + push = arr.push, + slice = arr.slice, + + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[ i ] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + // Support: Android<4.0 + // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + + // Can't trust NodeList.length + while ( ( target[ j++ ] = els[ i++ ] ) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && ( elem = newContext.getElementById( m ) ) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && + + // Support: IE 8 only + // Exclude object elements + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split( "|" ), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[ i ] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( ( cur = cur.nextSibling ) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return ( name === "input" || name === "button" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem && elem.namespaceURI, + docElem = elem && ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert( function( el ) { + el.className = "i"; + return !el.getAttribute( "className" ); + } ); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + } ); + + // ID filter and find + if ( support.getById ) { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find[ "TAG" ] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { + + var input; + + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll( "[name=d]" ).length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: Opera 10 - 11 only + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); + } + + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { + + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + } ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); + } : + function( a, b ) { + if ( b ) { + while ( ( b = b.parentNode ) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + // Choose the first element that is related to our preferred document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { + return -1; + } + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( ( cur = cur.parentNode ) ) { + ap.unshift( cur ); + } + cur = b; + while ( ( cur = cur.parentNode ) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[ i ] === bp[ i ] ) { + i++; + } + + return i ? + + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[ i ], bp[ i ] ) : + + // Otherwise nodes in our document sort first + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + /* eslint-disable max-len */ + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + /* eslint-enable max-len */ + + }; + }, + + "CHILD": function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + "not": markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element (issue #299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + "has": markFunction( function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + } ), + + "contains": markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); + }, + + "selected": function( elem ) { + + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos[ "empty" ]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo( function() { + return [ 0 ]; + } ), + + "last": createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + "even": createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "odd": createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rcombinators.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = uniqueCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert( function( el ) { + + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert( function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + } ); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert( function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + } ); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; + } + } ); +} + +return Sizzle; + +} )( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +} +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the primary Deferred + primary = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + primary.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, primary.done( updateFunc( i ) ).resolve, primary.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( primary.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return primary.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), primary.reject ); + } + + return primary.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +var rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + + // Support: Chrome 86+ + // In Chrome, if an element having a focusout handler is blurred by + // clicking outside of it, it invokes the handler synchronously. If + // that handler calls `.remove()` on the element, the data is cleared, + // leaving `result` undefined. We need to guard against this. + return result && result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + which: true +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + // Suppress native focus or blur as it's already being fired + // in leverageNative. + _default: function() { + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + // + // Support: Firefox 70+ + // Only Firefox includes border widths + // in computed dimensions. (gh-4529) + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate"; + tr.style.cssText = "border:1px solid"; + + // Support: Chrome 86+ + // Height set through cssText does not get applied. + // Computed height then comes back as 0. + tr.style.height = "1px"; + trChild.style.height = "9px"; + + // Support: Android 8 Chrome 86+ + // In our bodyBackground.html iframe, + // display for all div elements is set to "inline", + // which causes a problem only in Android 8 Chrome 86. + // Ensuring the div is display: block + // gets around this issue. + trChild.style.display = "block"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = ( parseInt( trStyle.height, 10 ) + + parseInt( trStyle.borderTopWidth, 10 ) + + parseInt( trStyle.borderBottomWidth, 10 ) ) === tr.offsetHeight; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( dataPriv.get( cur, "events" ) || Object.create( null ) )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = { guid: Date.now() }; + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml, parserErrorElem; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) {} + + parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ]; + if ( !xml || parserErrorElem ) { + jQuery.error( "Invalid XML: " + ( + parserErrorElem ? + jQuery.map( parserErrorElem.childNodes, function( el ) { + return el.textContent; + } ).join( "\n" ) : + data + ) ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ).filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ).map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + +originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script but not if jsonp + if ( !isSuccess && + jQuery.inArray( "script", s.dataTypes ) > -1 && + jQuery.inArray( "json", s.dataTypes ) < 0 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " + + + + + + + + + + + + + +
+
+
+
+ +
+

Architecture

+

Boxes.py it structured into several distinct tiers.

+
+

User Interfaces

+

User interfaces allow users to render the different generators. They +handle the parameters of Generators and convert them to a readable +form. The user interfaces are located in scripts/. Currently there is

+
    +
  • scripts/boxes – the command line interface

  • +
  • scripts/boxesserver – the web interface

  • +
  • scripts/boxes2inx – generates Inkscape extensions

  • +
  • scripts/boxes_example.ipynb – Jupyter notebook

  • +
+
+
+

Generators

+

A (box) generator is an sub class of boxes.Boxes. It generates one +drawing. The sub classes over load .__init__() to set their parameters +and implement .render() that does the actual drawing.

+

Generators are found in boxes/generators/. They are included into +the web UI and the CLI tool by the name of their class. So whenever +you copy either an existing generator or the sceleton in +boxes/generators/_template.py you need to change the name of the +main class first.

+
+
+

Parts

+

Parts are a single call that draws something according to a set of parameters. +There is a number of standard parts. Their typical params are +explained in the API docs.

+

Only real requirement for a part it supporting the move parameter for +placement.

+
+

Part Callbacks

+

Most parts support callbacks - either one in the middle for round +parts or one for each edge. They allow placing holes or other features +on the part.

+
+
+ +
+

Edges

+

Edges are turtle graphic commands. But they have been elevated to +proper Classes to handle outsets. They can be passed as parameters to parts. +There is a set of standard edges found in .edges. They are +associated with a single char which can be used instead of the +Edge object itself at most places. This allows passing the edge +description of a part as a string.

+
+
+

Turtle graphics

+

There are a few turtle graphics commands that do the actual +drawing. Corners with an positive angle (going counter clockwise) +close the part while negative angles (going clockwise) create protrusions. +This is inversed for holes which need to be drawn clockwise.

+

Getting this directions right is important to make the burn correction +(aka kerf) work properly.

+
+
+

Simple drawing commands

+

These also are simple drawing commands. Some of them get x, y and +angle parameters to draw somewhere specific. Some just draw right +at the current coordinate origin. Often these commands create holes or +hole patterns.

+
+
+

Back end

+

Boxes.py used to use cairo as graphics library. It now uses its own - +pure Python - back end. It is not fully encapsulated +within the drawing methods of the Boxes class. Although this is the +long term goal. Boxes.ctx is the context all drawing is made on.

+
+
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/api_arguments.html b/html/api_arguments.html new file mode 100644 index 0000000..12417aa --- /dev/null +++ b/html/api_arguments.html @@ -0,0 +1,241 @@ + + + + + + + + + Generator Arguments — boxes.py 0.1 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Generator Arguments

+

Boxes.py uses the argparse standard library for handling the +arguments for the generators. It is used directly for the boxes +command line tool. But it also handles – with some additional code – +the web interface and the Inkscape extensions. To make this work one +has to limit the kind of parameters used. Boxes.py supports the +following types:

+
+
    +
  • int

  • +
  • float

  • +
  • str

  • +
  • boxes.boolarg – an alternative to bool that works with the +web interface

  • +
  • boxes.argparseSections – multiple lengths e.g. for dividing up +a box in one direction

  • +
+
+

and

+
+
+class boxes.ArgparseEdgeType(edges=None)[source]
+

argparse type to select from a set of edge types

+
+ +

For the standard types there is code to create HTML and Inkscape +extensions. The other types can have .html() and .inx() +methods.

+

The argument parser need to be built in the .__init__() method +after calling the method of the super class. Have a look at

+
+
+BOX.__init__()[source]
+
+ +

As many arguments are used over and over there is a function that can +add the most common ones:

+
+
+Boxes.buildArgParser(*l, **kw)[source]
+

Add commonly used arguments

+
+
Parameters:
+
    +
  • *l – parameter names

  • +
  • **kw – parameters with new default values

  • +
+
+
+

Supported parameters are

+
    +
  • floats: x, y, h, hi

  • +
  • argparseSections: sx, sy, sh

  • +
  • ArgparseEdgeType: bottom_edge, top_edge

  • +
  • boolarg: outside

  • +
  • str (selection): nema_mount

  • +
+
+ +

Check the source for details about the single arguments.

+

Other arguments can be added with the normal argparser API - namely

+
+
+ArgumentParser.add_argument(dest, ..., name=value, ...)
+
+ArgumentParser.add_argument(option_string, option_string, ..., name=value, ...) None
+
+ +

of the Boxes.argparser attribute.

+
+

Edge style arguments

+

Edges that work together share a Settings class (and object). These +classes can create argparse groups:

+
+
+classmethod Settings.parserArguments(parser, prefix=None, **defaults)[source]
+
+ +

See

+
+
+BOX.__init__()[source]
+
+ +

for a list of possible edge settings. These regular settings are used +in the standard edge instances used everywhere. For special edge +instances you can call them with a prefix parameter. But you then +need to deal with the results on your own.

+
+
+

Default Arguments

+

The Default arguments get added automatically by the super class’s +constructor.

+
+
+

Accessing the Arguments

+

For convenience content of the arguments are written to attributes of +the Boxes instance before .render() is called. This is done by +Boxes.parseArgs. But most people won’t need to care as this is +handled by the frame work. Be careful to not overwrite important +methods or attributes by using conflicting argument names.

+
+
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/api_burn.html b/html/api_burn.html new file mode 100644 index 0000000..564875b --- /dev/null +++ b/html/api_burn.html @@ -0,0 +1,192 @@ + + + + + + + + + Burn correction — boxes.py 0.1 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Burn correction

+

The burn correction – aka kerf – is done in two separate steps. The +first mechanism is used during drawing. After rendering there is +a post processing step that replaces the inverted arcs of the inner corners by +Bezier loops that can be cut in a continous motion.

+

The first mechanism is integrated into the low level +commands of Boxes.py. So for the most part developers do not need to +care about it. Nevertheless they need to understand how it works to +catch the places the do need to care.

+

Burn correction is done by increasing the radius of all outer +corners. This moves all the straight lines outward by the same +amount. This has the added benefit of not needing to change the length +of the straight lines – making them independent of the adjacent +angles. An issue arises when it comes to inner corners. If they do +have a radius reducing it by the burn value does the right thing. But +for small radii and sharp corners (radius zero) this results in a +negative values. It turns out flipping over the arc for negative radii +allows keeping the lengths of the straight lines unchanged. So this is +what Boxes.py does:

+_images/burn.svg

This results in the straight lines touching the piece. This would lead to +overcuts that are not as nice as proper dog bones as might be used by +a dedicated CAM software. But as Boxes.py is meant to be used for laser +cutting this deemed acceptable for a long time:

+_images/overcuts.svg
+

Programmer’s perspective

+

For this to work it is important that outside is drawn in a counter +clock wise direction while holes are drawn in a clock wise direction.

+

boxes.Boxes.corner() adjusts the radius automatically +according to .burn. This propagates to higher level +functions. Parts shipped with Boxes.py do take the +burn out-set into account and execute callbacks at the correct position.

+

In case developers move to a feature inside of a part or executing +callbacks while implementing a part they need to be aware of the burn +correction. boxes.Boxes.cc() does correct for the out-set if +called without an y parameter. But if a value is given one has to +add self.burn to compensate. Note that the x value typically +does not have to be corrected as the callbacks are executed from right +underneath the part.

+

A similar approach is necessary when moving to a feature drawn inside +the part without the use of callbacks. Here you typically have to +correct for the out-set at the outside of the part and again for in-set +of the hole one is about to cut. This can be done in x or y +direction depending on whether the cut ist started vertical or +horizontally.

+
+
+

Replacing the inverted arcs

+

The inverted arcs have several drawbacks. For one they remove more +material than needed. This is not a big deal for laser cutters. But if +the boxes are cut with a CNC milling machine that can be +annoying. Another drawback is that the direction is reversed twice +which requires the tool (typically the laser head) to come to a total stop.

+

To solve this issue all paths are scanned for intersecting lines that +are connected by an inverted arc. There the lines are shortened to the +intersection point and the arc is replaced by a Bezier loop that is +continues the lines and loops on the outside of the corner. That way +the path still removes additional material to make sure the full +inner corner is cleared out. The current implementation uses the +former end points of the lines as control points. This gives +reasonable results but errs on the save side. The amount of material +removed can probably be further optimized.

+_images/burn2.svg
+
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/api_drawing.html b/html/api_drawing.html new file mode 100644 index 0000000..d34eb87 --- /dev/null +++ b/html/api_drawing.html @@ -0,0 +1,538 @@ + + + + + + + + + Drawing commands — boxes.py 0.1 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Drawing commands

+
+

Turtle Graphics commands

+

These commands all move the coordinate system with them.

+
+
+Boxes.edge(length, tabs=0)[source]
+

Simple line +:param length: length in mm

+
+ +
+
+Boxes.corner(degrees, radius=0, tabs=0)[source]
+

Draw a corner

+

This is what does the burn corrections

+
+
Parameters:
+
    +
  • degrees – angle

  • +
  • radius – (Default value = 0)

  • +
+
+
+
+ +
+
+Boxes.curveTo(x1, y1, x2, y2, x3, y3)[source]
+

control point 1, control point 2, end point

+
+
Parameters:
+
    +
  • x1

  • +
  • y1

  • +
  • x2

  • +
  • y2

  • +
  • x3

  • +
  • y3

  • +
+
+
+
+ +
+
+Boxes.polyline(*args)[source]
+

Draw multiple connected lines

+
+
Parameters:
+

*args – Alternating length in mm and angle in degrees.

+
+
+

lengths may be a tuple (length, #tabs) +angles may be tuple (angle, radius)

+
+ +
+

Special Functions

+
+
+Boxes.bedBoltHole(length, bedBoltSettings=None, tabs=0)[source]
+

Draw an edge with slot for a bed bolt

+
+
Parameters:
+
    +
  • length – length of the edge in mm

  • +
  • bedBoltSettings – (Default value = None) Dimmensions of the slot

  • +
+
+
+
+ +
+
+

Latch and Grip

+

These should probably be Edge classes. But right now they are still functions.

+
+
+Boxes.grip(length, depth)[source]
+

Corrugated edge useful as an gipping area

+
+
Parameters:
+
    +
  • length – length

  • +
  • depth – depth of the grooves

  • +
+
+
+
+ +
+
+Boxes.latch(length, positive=True, reverse=False)[source]
+

Latch to fix a flex box door to the box

+
+
Parameters:
+
    +
  • length – length in mm

  • +
  • positive – (Default value = True) False: Door side; True: Box side

  • +
  • reverse – (Default value = False) True when running away from the latch

  • +
+
+
+
+ +
+
+Boxes.handle(x, h, hl, r=30)[source]
+

Creates an Edge with a handle

+
+
Parameters:
+
    +
  • x – width in mm

  • +
  • h – height in mm

  • +
  • hl – height if th grip hole

  • +
  • r – (Default value = 30) radius of the corners

  • +
+
+
+
+ +
+
+

Tab support

+

Tabs are small interuptions in the border of a part to keep it in +place. They are enabled with the tabs parameter. All +Edges automatically create about two tabs. So parts like +boxes.Boxes.rectangularWall() will have 8 tabs holding them +in place. Because of this developers often don’t need to be concerned +about tabs. But some part may be completely drawn by low level Turtle +Graphics commands. For those both boxes.Boxes.edge() and +boxes.Boxes.corner() do support a tabs parameter. In +addition the length of the line segments in boxes.Boxes.polyline() can +be given as a tuple (length, tabs).

+
+
+
+

Draw Commands

+

These commands do not change the coordinate system but get the +coordinates passed as parameters. All of them are either som sort of +hole or text. These artifacts are placed somewhere independently of +some continuous outline of the part their on.

+
+
+Boxes.hole(x, y, r=0.0, d=0.0, tabs=0)[source]
+

Draw a round hole

+
+
Parameters:
+
    +
  • x – position

  • +
  • y – postion

  • +
  • r – radius

  • +
+
+
+
+ +
+
+Boxes.rectangularHole(x, y, dx, dy, r=0, center_x=True, center_y=True)[source]
+

Draw a rectangular hole

+
+
Parameters:
+
    +
  • x – position

  • +
  • y – position

  • +
  • dx – width

  • +
  • dy – height

  • +
  • r – (Default value = 0) radius of the corners

  • +
  • center_x – (Default value = True) if True, x position is the center, else the start

  • +
  • center_y – (Default value = True) if True, y position is the center, else the start

  • +
+
+
+
+ +
+
+Boxes.dHole(x, y, r=None, d=None, w=None, rel_w=0.75, angle=0)[source]
+

Draw a hole for a shaft with flat edge - D shaped hole

+
+
Parameters:
+
    +
  • x – center position

  • +
  • y – center position

  • +
  • r – radius (overrides d)

  • +
  • d – diameter

  • +
  • w – width measured against flat side in mm

  • +
  • rel_w – width in percent

  • +
  • angle – orentation (rotation) of the flat side

  • +
+
+
+
+ +
+
+Boxes.flatHole(x, y, r=None, d=None, w=None, rel_w=0.75, angle=0)[source]
+

Draw a hole for a shaft with two opposed flat edges - ( ) shaped hole

+
+
Parameters:
+
    +
  • x – center position

  • +
  • y – center position

  • +
  • r – radius (overrides d)

  • +
  • d – diameter

  • +
  • w – width measured against flat side in mm

  • +
  • rel_w – width in percent

  • +
  • angle – orientation (rotation) of the flat sides

  • +
+
+
+
+ +
+
+Boxes.text(text, x=0, y=0, angle=0, align='', fontsize=10, color=[0.0, 0.0, 0.0], font='Arial')[source]
+

Draw text

+
+
Parameters:
+
    +
  • text – text to render

  • +
  • x – (Default value = 0)

  • +
  • y – (Default value = 0)

  • +
  • angle – (Default value = 0)

  • +
  • align – (Default value = “”) string with combinations of (top|middle|bottom) and (left|center|right) separated by a space

  • +
+
+
+
+ +
+
+Boxes.NEMA(size, x=0, y=0, angle=0, screwholes=None)[source]
+

Draw holes for mounting a NEMA stepper motor

+
+
Parameters:
+
    +
  • size – Nominal size in tenths of inches

  • +
  • x – (Default value = 0)

  • +
  • y – (Default value = 0)

  • +
  • angle – (Default value = 0)

  • +
+
+
+
+ +
+
+Boxes.TX(size, x=0, y=0, angle=0)[source]
+

Draw a star pattern

+
+
Parameters:
+
    +
  • size – 1 to 100

  • +
  • x – (Default value = 0)

  • +
  • y – (Default value = 0)

  • +
  • angle – (Default value = 0)

  • +
+
+
+
+ +
+
+Boxes.flex2D(x, y, width=1)[source]
+

Fill a rectangle with a pattern allowing bending in both axis

+
+
Parameters:
+
    +
  • x – width

  • +
  • y – height

  • +
  • width – width between the lines of the pattern in multiples of thickness

  • +
+
+
+
+ +
+
+class NutHole
+
+ +

An instance is available as boxes.Boxes.nutHole()

+

An instance of

+
+
+class boxes.edges.FingerHoles(boxes, settings)[source]
+

Hole matching a finger joint edge

+
+ +

is accessible as Boxes.fingerHolesAt.

+
+

Hexagonal Hole patterns

+

Hexagonal hole patterns are one way to have some ventilation for +housings made with Boxes.py. Right now both .rectangularWall() +and .roundedPlate() do supports this pattern directly by passing +the parameters to the calls. For other use cases these more low level +methods can be used.

+

For now this is the only supported pattern for ventilation slots. More +may be added in the future.

+

There is a global Boxes.hexHolesSettings object that is used if no settings are +passed. It currently is just a tuple of (r, dist, style) defaulting to +(5, 3, ‘circle’) but might be replace by a Settings instance in the future.

+
+
+Boxes.hexHolesRectangle(x, y, settings=None, skip=None)[source]
+

Fills a rectangle with holes in a hex pattern.

+

Settings have: +r : radius of holes +b : space between holes +style : what types of holes (not yet implemented)

+
+
Parameters:
+
    +
  • x – width

  • +
  • y – height

  • +
  • settings – (Default value = None)

  • +
  • skip – (Default value = None) function to check if hole should be present +gets x, y, r, b, posx, posy

  • +
+
+
+
+ +
+
+Boxes.hexHolesCircle(d, settings=None)[source]
+

Fill circle with holes in a hex pattern

+
+
Parameters:
+
    +
  • d – diameter of the circle

  • +
  • settings – (Default value = None)

  • +
+
+
+
+ +
+
+Boxes.hexHolesPlate(x, y, rc, settings=None)[source]
+

Fill a plate with holes in a hex pattern

+
+
Parameters:
+
    +
  • x – width

  • +
  • y – height

  • +
  • rc – radius of the corners

  • +
  • settings – (Default value = None)

  • +
+
+
+
+ +
+
+Boxes.hexHolesHex(h, settings=None, grow=None)[source]
+

Fill a hexagon with holes in a hex pattern

+
+
Parameters:
+
    +
  • h – height

  • +
  • settings – (Default value = None)

  • +
  • grow – (Default value = None)

  • +
+
+
+
+ +
+
+
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/api_edges.html b/html/api_edges.html new file mode 100644 index 0000000..22960ad --- /dev/null +++ b/html/api_edges.html @@ -0,0 +1,777 @@ + + + + + + + + + Edges — boxes.py 0.1 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Edges

+

Edges are what makes Boxes.py work. They draw a – more or less – straight +border to the current piece. They are part of the turtle graphics part +of Boxes.py. This means they start at the current position and current +direction and move the current position to the end of the edge.

+

Edge instances have a Settings object associated with them that keeps +the details about how the edge should look like. Edges that are +supposed to work together share the same Settings object to ensure +they fit together - assuming they have the same length. Most edges are +symetrical to unsure they fit together even when drawn from different +directions. Although there are a few exception - mainly edges that +provide special features like hinges.

+

As edges started out as methods of the main Boxes class they still are +callables. It turned out that the edges need to provide a bit more +information to allow the surrounding code to handle them +properly. When drawing an Edge there is a virtual straight line that +is the border the shape of the part (e.g. an rectangle). But the +actual Edge has often to be drawn elsewhere. Best example if probably +the F Edge that matches the normal finger joints. It has to start +one material thickness outside of the virual border of the part so the +cutouts for the opposing fingers just touch the border. The Edge +classes have a number of methods to deal with these kind of offsets.

+

A set of instances are kept the .edges attribute of the +Boxes class. It is a dict with strings of length one as keys:

+
    +
  • aAbB : reserved to be used in generators

  • +
  • c : ClickConnector

  • +
  • C : ClickEdge

  • +
  • d : DoveTailJoint

  • +
  • D : DoveTailJointCounterPart

  • +
  • e : Edge

  • +
  • E : OutSetEdge

  • +
  • f : FingerJointEdge

  • +
  • F : FingerJointEdgeCounterPart

  • +
  • g : GrippingEdge

  • +
  • G : MountingEdge

  • +
  • h : FingerHoleEdge

  • +
  • ijk : Hinge (start, end, both sides)

  • +
  • IJK : HingePin (start, end, both sides)

  • +
  • L : LidHoleEdge

  • +
  • l : LidEdge

  • +
  • M : LidSideLeft

  • +
  • m : LidLeft

  • +
  • N : LidSideRight

  • +
  • n : LidRight

  • +
  • Oo : ChestHinge

  • +
  • Pp : ChestHingeTop

  • +
  • Q : ChestHingeFront

  • +
  • q : ChestHingePin

  • +
  • R : RackEdge

  • +
  • s : StackableEdge

  • +
  • S : StackableEdgeTop

  • +
  • š : StackableFeet

  • +
  • Š : StackableHoleEdgeTop

  • +
  • T : RoundedTriangleFingerHolesEdge

  • +
  • t : RoundedTriangleEdge

  • +
  • uUvV : CabinetHingeEdge

  • +
  • X : FlexEdge

  • +
  • y : HandleEdge

  • +
  • Y : HandleHoleEdge

  • +
  • Z : GroovedEdgeCounterPart

  • +
  • z : GroovedEdge

  • +
+
+

Edge base class

+
+
+class boxes.edges.BaseEdge(boxes, settings)[source]
+

Abstract base class for all Edges

+
+
+endAngle()[source]
+

Not yet supported

+
+ +
+
+margin()[source]
+

Space needed right of the starting point

+
+ +
+
+spacing()[source]
+

Space the edge needs outside of the inner space of the part

+
+ +
+
+startAngle()[source]
+

Not yet supported

+
+ +
+
+startwidth()[source]
+

Amount of space the beginning of the edge is set below the inner space of the part

+
+ +
+ +
+
+abstract BaseEdge.__call__(length, **kw)[source]
+

Call self as a function.

+
+ +
+
+

Settings Class

+
+
+class boxes.edges.Settings(thickness, relative=True, **kw)[source]
+

Generic Settings class

+

Used by different other classes to store measurements and details. +Supports absolute values and settings that grow with the thickness +of the material used.

+

Overload the absolute_params and relative_params class attributes with +the suported keys and default values. The values are available via +attribute access.

+
+
+checkValues()[source]
+

Check if all values are in the right range. Raise ValueError if needed

+
+ +
+
+edgeObjects(boxes, chars='', add=True)[source]
+

Generate Edge objects using this kind of settings

+
+
Parameters:
+
    +
  • boxes – Boxes object

  • +
  • chars – sequence of chars to be used by Edge objects

  • +
  • add – add the resulting Edge objects to the Boxes object’s edges

  • +
+
+
+
+ +
+
+setValues(thickness, relative=True, **kw)[source]
+

Set values

+
+
Parameters:
+
    +
  • thickness – thickness of the material used

  • +
  • relative – (Default value = True) Do scale by thickness

  • +
  • **kw – parameters to set

  • +
+
+
+
+ +
+ +
+
+

Straight Edges

+
+
+class boxes.edges.Edge(boxes, settings)[source]
+

Straight edge

+
+ +
+
+class boxes.edges.OutSetEdge(boxes, settings)[source]
+

Straight edge out set by one thickness

+
+ +
+
+

Grip

+
+
+class boxes.edges.GripSettings(thickness, relative=True, **kw)[source]
+

Settings for GrippingEdge +Values:

+
    +
  • absolute_params

  • +
+
+
    +
  • style : “wave : “wave” or “bumps”

  • +
  • outset : True : extend outward the straight edge

  • +
+
+
    +
  • relative (in multiples of thickness)

  • +
+
+
    +
  • depth : 0.3 : depth of the grooves

  • +
+
+
+ +
+
+class boxes.edges.GrippingEdge(boxes, settings)[source]
+
+ +
+
+

Stackable Edges

+
+
+class boxes.edges.StackableEdge(boxes, settings, fingerjointsettings)[source]
+

Edge for having stackable Boxes. The Edge creates feet on the bottom +and has matching recesses on the top corners.

+
+ +
+
+class boxes.edges.StackableEdgeTop(boxes, settings, fingerjointsettings)[source]
+
+ +
+

Stackable Edge Settings

+
+
+class boxes.edges.StackableSettings(thickness, relative=True, **kw)[source]
+

Settings for Stackable Edges

+

Values:

+
    +
  • absolute_params

    +
      +
    • angle : 60 : inside angle of the feet

    • +
    +
  • +
  • relative (in multiples of thickness)

    +
      +
    • height : 2.0 : height of the feet (multiples of thickness)

    • +
    • width : 4.0 : width of the feet (multiples of thickness)

    • +
    • holedistance : 1.0 : distance from finger holes to bottom edge (multiples of thickness)

    • +
    +
  • +
+
+
+checkValues()[source]
+

Check if all values are in the right range. Raise ValueError if needed

+
+ +
+
+edgeObjects(boxes, chars='sSšŠ', add=True, fingersettings=None)[source]
+

Generate Edge objects using this kind of settings

+
+
Parameters:
+
    +
  • boxes – Boxes object

  • +
  • chars – sequence of chars to be used by Edge objects

  • +
  • add – add the resulting Edge objects to the Boxes object’s edges

  • +
+
+
+
+ +
+ +
+
+
+

Finger joints

+

Finger joints are a simple way of joining two sheets (e.g. of plywood). They +work best at an 90° angle. There are two different sides matching each +other. As a third alternative there are holes that the fingers of one +sheet can plug into. This allows stable T connections especially +useful for inner walls.

+
+
+class boxes.edges.FingerJointEdge(boxes, settings)[source]
+

Finger joint edge

+
+ +
+
+class boxes.edges.FingerJointEdgeCounterPart(boxes, settings)[source]
+

Finger joint edge - other side

+
+ +
+
+class boxes.edges.FingerHoleEdge(boxes, fingerHoles=None, **kw)[source]
+

Edge with holes for a parallel finger joint

+
+ +
+
+class boxes.edges.CrossingFingerHoleEdge(boxes, height, fingerHoles=None, **kw)[source]
+

Edge with holes for finger joints 90° above

+
+ +

In addition there is

+
+
+class boxes.edges.FingerHoles(boxes, settings)[source]
+

Hole matching a finger joint edge

+
+ +

which is no Edge but fits FingerJointEdge.

+

An instance of is accessible as Boxes.fingerHolesAt.

+
+

Finger Joint Settings

+
+
+class boxes.edges.FingerJointSettings(thickness, relative=True, **kw)[source]
+

Settings for Finger Joints

+

Values:

+
    +
  • absolute +* style : “rectangular” : style of the fingers +* surroundingspaces : 2.0 : space at the start and end in multiple of normal spaces +* angle: 90 : Angle of the walls meeting

  • +
  • relative (in multiples of thickness)

    +
      +
    • space : 2.0 : space between fingers (multiples of thickness)

    • +
    • finger : 2.0 : width of the fingers (multiples of thickness)

    • +
    • width : 1.0 : width of finger holes (multiples of thickness)

    • +
    • edge_width : 1.0 : space below holes of FingerHoleEdge (multiples of thickness)

    • +
    • play : 0.0 : extra space to allow finger move in and out (multiples of thickness)

    • +
    • extra_length : 0.0 : extra material to grind away burn marks (multiples of thickness)

    • +
    +
  • +
+
+
+checkValues()[source]
+

Check if all values are in the right range. Raise ValueError if needed

+
+ +
+
+edgeObjects(boxes, chars='fFh', add=True)[source]
+

Generate Edge objects using this kind of settings

+
+
Parameters:
+
    +
  • boxes – Boxes object

  • +
  • chars – sequence of chars to be used by Edge objects

  • +
  • add – add the resulting Edge objects to the Boxes object’s edges

  • +
+
+
+
+ +
+ +
+
+

Bed Bolts

+
+
+class boxes.edges.BoltPolicy[source]
+

Abstract class

+

Distributes (bed) bolts on a number of segments +(fingers of a finger joint)

+
+ +
+
+class boxes.edges.Bolts(bolts=1)[source]
+

Distribute a fixed number of bolts evenly

+
+ +
+
+
+

Dove Tail Joints

+

Dovetails joints can only be used to join two pieces flatly. This +limits their use to closing some round form created with flex areas or +for joining several parts to a bigger one. For this use case they are +much stronger than simple finger joints and can also bare pulling forces.

+
+
+class boxes.edges.DoveTailJoint(boxes, settings)[source]
+

Edge with dove tail joints

+
+ +
+
+class boxes.edges.DoveTailJointCounterPart(boxes, settings)[source]
+

Edge for other side of dove joints

+
+ +
+

Dove Tail Settings

+
+
+class boxes.edges.DoveTailSettings(thickness, relative=True, **kw)[source]
+

Settings for Dove Tail Joints

+

Values:

+
    +
  • absolute

    +
      +
    • angle : 50 : how much should fingers widen (-80 to 80)

    • +
    +
  • +
  • relative (in multiples of thickness)

    +
      +
    • size : 3 : from one middle of a dove tail to another (multiples of thickness)

    • +
    • depth : 1.5 : how far the dove tails stick out of/into the edge (multiples of thickness)

    • +
    • radius : 0.2 : radius used on all four corners (multiples of thickness)

    • +
    +
  • +
+
+
+edgeObjects(boxes, chars='dD', add=True)[source]
+

Generate Edge objects using this kind of settings

+
+
Parameters:
+
    +
  • boxes – Boxes object

  • +
  • chars – sequence of chars to be used by Edge objects

  • +
  • add – add the resulting Edge objects to the Boxes object’s edges

  • +
+
+
+
+ +
+ +
+
+
+

Flex

+
+
+class boxes.edges.FlexEdge(boxes, settings)[source]
+

Edge with flex cuts - use straight edge for the opposing side

+
+ +
+

Flex Settings

+
+
+class boxes.edges.FlexSettings(thickness, relative=True, **kw)[source]
+

Settings for Flex

+

Values:

+
    +
  • absolute

  • +
+
+
    +
  • stretch : 1.05 : Hint of how much the flex part should be shortend

  • +
+
+
    +
  • relative (in multiples of thickness)

  • +
+
+
    +
  • distance : 0.5 : width of the pattern perpendicular to the cuts (multiples of thickness)

  • +
  • connection : 1.0 : width of the gaps in the cuts (multiples of thickness)

  • +
  • width : 5.0 : width of the pattern in direction of the cuts (multiples of thickness)

  • +
+
+
+ +
+
+
+

Slots

+
+
+class boxes.edges.Slot(boxes, depth)[source]
+

Edge with an slot to slid another pice through

+
+ +
+
+class boxes.edges.SlottedEdge(boxes, sections, edge='e', slots=0)[source]
+

Edge with multiple slots

+
+ +
+
+

CompoundEdge

+
+
+class boxes.edges.CompoundEdge(boxes, types, lengths)[source]
+

Edge composed of multiple different Edges

+
+ +
+
+

Hinges

+
+

Hinge Settings

+
+
+class boxes.edges.HingeSettings(thickness, relative=True, **kw)[source]
+

Settings for Hinges and HingePins +Values:

+
    +
  • absolute_params

  • +
+
+
    +
  • style : “outset” : “outset” or “flush”

  • +
  • outset : False : have lid overlap at the sides (similar to OutSetEdge)

  • +
  • pinwidth : 1.0 : set to lower value to get disks surrounding the pins

  • +
  • grip_percentage” : 0 : percentage of the lid that should get grips

  • +
+
+
    +
  • relative (in multiples of thickness)

  • +
+
+
    +
  • hingestrength : 1 : thickness of the arc holding the pin in place (multiples of thickness)

  • +
  • axle : 2 : diameter of the pin hole (multiples of thickness)

  • +
  • grip_length : 0 : fixed length of the grips on he lids (multiples of thickness)

  • +
+
+
+ +
+
+

Hinge

+
+
+class boxes.edges.Hinge(boxes, settings=None, layout=1)[source]
+
+ +
+
+

HingePin

+
+
+class boxes.edges.HingePin(boxes, settings=None, layout=1)[source]
+
+ +
+
+
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/api_examples.html b/html/api_examples.html new file mode 100644 index 0000000..01b9469 --- /dev/null +++ b/html/api_examples.html @@ -0,0 +1,248 @@ + + + + + + + + + Examples — boxes.py 0.1 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Examples

+

Decide whether you want to start from scratch or want to rework an +existing generator.

+

You should go over the arguments first. Get at least the most basic +arguments done. For things you are still unsure you can just use a +attribute set in the .__init__() method and turn it into a proper +argument later on.

+

Depending on what you want to do you can work on the different levels +of the API. You can either use what is there and combine it into +something new or you can implements new things in the appropriate level.

+

Here are some examples:

+
+

Housing for some electronics

+

You can use the ElectronicsBox or the ClosedBox as a basis. Write some +callbacks to place holes in the walls to allow accessing the ports of +the electronics boards. Place some holes to screw spacers into the +bottom to mount the PBC on.

+
+
+

NemaMount

+

This is a good non box example to look at.

+
+
+class boxes.generators.nemamount.NemaMount[source]
+

Mounting braket for a Nema motor

+
+ +

Note that although it produces a cube like object it uses separate +variables (x, y, h) for the different axis. Probably +because it started as a copy of another generator like ClosedBox.

+
+
+

DisplayShelf

+
+
+class boxes.generators.displayshelf.DisplayShelf[source]
+

Shelf with slanted floors

+
+ +

The DisplayShelf is completely made out of rectangularWalls(). It uses +a callback to place all the fingerHolesAt() right places on the sides. +While the use of the Boxes.py API is pretty straight forward the +calculations needed are a bit more tricky. You can use the debug +default param to check if you got things right when attempting +something like this yourself.

+

Note that the front walls and the shelfs form a 90° angle so they work +with the default FingerJoints.

+
+
+

BinTray

+
+
+class boxes.generators.bintray.BinTray[source]
+

A Type tray variant to be used up right with sloped walls in front

+
+ +

The BinTray is based on the TypeTray generator:

+
+
+class boxes.generators.typetray.TypeTray[source]
+

Type tray - allows only continuous walls

+
+ +

TypeTray is an already pretty complicated generator.

+

BinTray replaces the now vertical front (former top) edges with a +special purpose one that does add the triangles:

+
+
+class boxes.generators.bintray.BinFrontEdge(boxes, settings)[source]
+
+ +

The hi (height of inner walls) argument was removed although the +variable is still used internally - out of laziness.

+

To complete the bin the front walls are added. Follow up patches then +switched the slots between the vertical and horizontal walls to have +better support for the now bottoms of the bins. Another patch adds +angled finger joints for connecting the front walls with the bottoms +of the bins.

+

The TrafficLight generator uses a similar technique implementing its +own Edge class. But it uses its own code to generate all the wall needed.

+
+
+

Stachel

+
+
+class boxes.generators.stachel.Stachel[source]
+

Bass Recorder Endpin

+
+ +

Stachel allows mounting a monopod to a bass recorder. It is basically +just one part repeated with different parameters. It can’t really make +use of much of the Boxes.py library. It implements this one part +including the move parameter and draws everything using the +.polyline() method. This is pretty painful as lots of angles and +distances need to be calculated by hand.

+

For symmetric sections it passes the parameters to .polyline twice +– first in normal order and then reversed to get the mirrored section.

+

This generator is beyond what Boxes.py is designed for. If you need +something similar you may want to use another tool like OpenScad or a +traditional CAD program.

+
+
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/api_existing_parts.html b/html/api_existing_parts.html new file mode 100644 index 0000000..f22fd4f --- /dev/null +++ b/html/api_existing_parts.html @@ -0,0 +1,376 @@ + + + + + + + + + Existing Parts — boxes.py 0.1 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Existing Parts

+

A couple of commands can create whole parts like walls. Typically the +sizes given are the inner dimension not including additional space +needed for burn compensation or joints.

+

Currently there are the following parts:

+
+
+Boxes.rectangularWall(x, y, edges='eeee', ignore_widths=[], holesMargin=None, holesSettings=None, bedBolts=None, bedBoltSettings=None, callback=None, move=None, label='')[source]
+

Rectangular wall for all kind of box like objects

+
+
Parameters:
+
    +
  • x – width

  • +
  • y – height

  • +
  • edges – (Default value = “eeee”) bottom, right, top, left

  • +
  • ignore_widths – list of edge_widths added to adjacent edge

  • +
  • holesMargin – (Default value = None)

  • +
  • holesSettings – (Default value = None)

  • +
  • bedBolts – (Default value = None)

  • +
  • bedBoltSettings – (Default value = None)

  • +
  • callback – (Default value = None)

  • +
  • move – (Default value = None)

  • +
  • label – rendered to identify parts, it is not ment to be cut or etched (Default value = “”)

  • +
+
+
+
+ +
+
+Boxes.flangedWall(x, y, edges='FFFF', flanges=None, r=0.0, callback=None, move=None, label='')[source]
+

Rectangular wall with flanges extending the regular size

+

This is similar to the rectangularWall but it may extend to either +side. Sides with flanges may only have e, E, or F edges - the later +being replaced with fingerHoles.

+
+
Parameters:
+
    +
  • x – width

  • +
  • y – height

  • +
  • edges – (Default value = “FFFF”) bottom, right, top, left

  • +
  • flanges – (Default value = None) list of width of the flanges

  • +
  • r – radius of the corners of the flange

  • +
  • callback – (Default value = None)

  • +
  • move – (Default value = None)

  • +
  • label – rendered to identify parts, it is not ment to be cut or etched (Default value = “”)

  • +
+
+
+
+ +
+
+Boxes.rectangularTriangle(x, y, edges='eee', r=0.0, num=1, bedBolts=None, bedBoltSettings=None, callback=None, move=None, label='')[source]
+

Rectangular triangular wall

+
+
Parameters:
+
    +
  • x – width

  • +
  • y – height

  • +
  • edges – (Default value = “eee”) bottom, right[, diagonal]

  • +
  • r – radius towards the hypothenuse

  • +
  • num – (Default value = 1) number of triangles

  • +
  • bedBolts – (Default value = None)

  • +
  • bedBoltSettings – (Default value = None)

  • +
  • callback – (Default value = None)

  • +
  • move – (Default value = None)

  • +
  • label – rendered to identify parts, it is not ment to be cut or etched (Default value = “”)

  • +
+
+
+
+ +
+
+Boxes.regularPolygonWall(corners=3, r=None, h=None, side=None, edges='e', hole=None, callback=None, move=None)[source]
+

Create regular polygon as a wall

+
+
Parameters:
+
    +
  • corners – number of corners of the polygon

  • +
  • radius – distance center to one of the corners

  • +
  • h – distance center to one of the sides (height of sector)

  • +
  • side – length of one side

  • +
  • edges – (Default value = “e”, may be string/list of length corners)

  • +
  • hole – diameter of central hole (Default value = 0)

  • +
  • callback – (Default value = None, middle=0, then sides=1..)

  • +
  • move – (Default value = None)

  • +
+
+
+
+ +
+
+Boxes.polygonWall(borders, edge='f', turtle=False, callback=None, move=None, label='')[source]
+

Polygon wall for all kind of multi-edged objects

+
+
Parameters:
+
    +
  • borders – array of distance and angles to draw

  • +
  • edge – (Default value = “f”) Edges to apply. If the array of borders contains more segments that edges, the edge will wrap. Only edge types without start and end width suppported for now.

  • +
  • turtle – (Default value = False)

  • +
  • callback – (Default value = None)

  • +
  • move – (Default value = None)

  • +
  • label – rendered to identify parts, it is not ment to be cut or etched (Default value = “”)

  • +
+
+
+
+ +
+
+Boxes.roundedPlate(x, y, r, edge='f', callback=None, holesMargin=None, holesSettings=None, bedBolts=None, bedBoltSettings=None, wallpieces=1, extend_corners=True, move=None)[source]
+

Plate with rounded corner fitting to .surroundingWall()

+

For the callbacks the sides are counted depending on wallpieces

+
+
Parameters:
+
    +
  • x – width

  • +
  • y – height

  • +
  • r – radius of the corners

  • +
  • callback – (Default value = None)

  • +
  • holesMargin – (Default value = None) set to get hex holes

  • +
  • holesSettings – (Default value = None)

  • +
  • bedBolts – (Default value = None)

  • +
  • bedBoltSettings – (Default value = None)

  • +
  • wallpieces – (Default value = 1) # of separate surrounding walls

  • +
  • extend_corners – (Default value = True) have corners outset with the edges

  • +
  • move – (Default value = None)

  • +
+
+
+
+ +
+
+Boxes.surroundingWall(x, y, r, h, bottom='e', top='e', left='D', right='d', pieces=1, extend_corners=True, callback=None, move=None)[source]
+

Wall(s) with flex fiting around a roundedPlate()

+

For the callbacks the sides are counted depending on pieces

+
+
Parameters:
+
    +
  • x – width of matching roundedPlate

  • +
  • y – height of matching roundedPlate

  • +
  • r – corner radius of matching roundedPlate

  • +
  • h – inner height of the wall (without edges)

  • +
  • bottom – (Default value = ‘e’) Edge type

  • +
  • top – (Default value = ‘e’) Edge type

  • +
  • left – (Default value = ‘D’) left edge(s)

  • +
  • right – (Default value = ‘d’) right edge(s)

  • +
  • pieces – (Default value = 1) number of separate pieces

  • +
  • callback – (Default value = None)

  • +
  • move – (Default value = None)

  • +
+
+
+
+ +
+

Parts Class

+

More parts are available in a separete class. An instance is available as +Boxes.parts

+
+
+Parts.disc(diameter, hole=0, callback=None, move='', label='')[source]
+

Simple disc

+
+
Parameters:
+
    +
  • diameter – diameter of the disc

  • +
  • hole – (Default value = 0)

  • +
  • callback – (Default value = None) called in the center

  • +
  • move – (Defaultvalue = None)

  • +
+
+
+
+ +
+
+Parts.waivyKnob(diameter, n=20, angle=45, hole=0, callback=None, move='')[source]
+

Disc with a waivy edge to be easier to be gripped

+
+
Parameters:
+
    +
  • diameter – diameter of the knob

  • +
  • n – (Default value = 20) number of waves

  • +
  • angle – (Default value = 45) maximum angle of the wave

  • +
  • hole – (Default value = 0)

  • +
  • callback – (Default value = None) called in the center

  • +
  • move – (Defaultvalue = None)

  • +
+
+
+
+ +
+
+Parts.concaveKnob(diameter, n=3, rounded=0.2, angle=70, hole=0, callback=None, move='')[source]
+

Knob with dents to be easier to be gripped

+
+
Parameters:
+
    +
  • diameter – diameter of the knob

  • +
  • n – (Default value = 3) number of dents

  • +
  • rounded – (Default value = 0.2) proportion of circumferen remaining

  • +
  • angle – (Default value = 70) angle the dents meet the circumference

  • +
  • hole – (Default value = 0)

  • +
  • callback – (Default value = None) called in the center

  • +
  • move – (Defaultvalue = None)

  • +
+
+
+
+ +
+
+Parts.ringSegment(r_outside, r_inside, angle, n=1, move=None)[source]
+

Ring Segment

+
+
Parameters:
+
    +
  • r_outside – outer radius

  • +
  • r_inside – inner radius

  • +
  • angle – anlge the segment is spanning

  • +
  • n – (Default value = 1) number of segments

  • +
  • move – (Defaultvalue = None)

  • +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/api_generator.html b/html/api_generator.html new file mode 100644 index 0000000..be24086 --- /dev/null +++ b/html/api_generator.html @@ -0,0 +1,244 @@ + + + + + + + + + Generators — boxes.py 0.1 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Generators

+

Generators are sub classes of

+
+
+class boxes.Boxes[source]
+

Main class – Generator should sub class this

+
+ +

Most code is directly in this class. Sub class are supposed to over +write the .__init__() and .render() method.

+

The Boxes class keeps a canvas object (self.ctx) that all +drawing is made on. In addition it keeps a couple of global settings +used for various drawing operations. See the .__init__() method +for the details.

+

For implementing a new generator forking an existing one or using the +boxes/generators/_template.py is probably easier than starting +from scratch.

+

Many methods and attributes are for use of the sub classes. These +methods are the interface for the user interfaces to interact with the +generators:

+
+
+Boxes.__init__()[source]
+
+ +
+
+Boxes.parseArgs(args=None)[source]
+

Parse command line parameters

+
+
Parameters:
+

args – (Default value = None) parameters, None for using sys.argv

+
+
+
+ +
+
+Boxes.render()[source]
+

Implement this method in your sub class.

+

You will typically need to call .parseArgs() before calling this one

+
+ +
+
+Boxes.open()[source]
+

Prepare for rendering

+

Create canvas and edge and other objects +Call this before .render()

+
+ +
+
+Boxes.close()[source]
+

Finish rendering

+

Flush canvas to disk and convert output to requested format if needed. +Call after .render()

+
+ +
+

Handling Generators

+

To handle the generators there is code in the boxes.generators +package.

+
+
+class boxes.generators.UIGroup(name, title=None, description='', image='')[source]
+
+
+add(box)[source]
+
+ +
+
+property image
+
+ +
+
+property thumbnail
+
+ +
+ +
+
+boxes.generators.getAllBoxGenerators()[source]
+
+ +
+
+boxes.generators.getAllGeneratorModules()[source]
+
+ +

This adds generators to the user interfaces automatically. For this to +work it is important that the class names are unique. So whenever you +start a new generator please change the class name right away.

+
+
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/api_navigation.html b/html/api_navigation.html new file mode 100644 index 0000000..22cd3d4 --- /dev/null +++ b/html/api_navigation.html @@ -0,0 +1,194 @@ + + + + + + + + + Navigation — boxes.py 0.1 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ + + + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/api_parts.html b/html/api_parts.html new file mode 100644 index 0000000..b2bbb5b --- /dev/null +++ b/html/api_parts.html @@ -0,0 +1,274 @@ + + + + + + + + + Parts — boxes.py 0.1 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Parts

+

There are a few parameter shared by many of those parts:

+
+

The callback parameter

+

The callback parameter can take on of the following forms:

+
    +
  • A function (or bound method) that expects one parameter: the number of the side the callback is currently called for.

  • +
  • A dict with some of the numbers of the sides as keys and functions without parameters as values.

  • +
  • A list of functions without parameters. The list may contain None as place holder and be shorter than the number of sides.

  • +
+

The callback functions are called with the side of the part at the +positive x and y axis. If the edge uses up space this space is below +the x axis. You do not have to restore the coordinate settings in the +callback.

+

Instead of functions it can be handy to use a lambda expression +calling the one building block function you need (e.g. fingerHolesAt).

+

For your own parts you can use this helper function:

+
+
+Boxes.cc(callback, number, x=0.0, y=None)[source]
+

Call callback from edge of a part

+
+
Parameters:
+
    +
  • callback – callback (callable or list of callables)

  • +
  • number – number of the callback

  • +
  • x – (Default value = 0.0) x position to be call on

  • +
  • y – (Default value = None) y position to be called on (default does burn correction)

  • +
+
+
+
+ +

For finding the right piece to the callback parameter this function is used:

+
+
+Boxes.getEntry(param, idx)[source]
+

Get entry from list or items itself

+
+
Parameters:
+
    +
  • param – list or item

  • +
  • idx – index in list

  • +
+
+
+
+ +
+
+

The move parameter

+

For placing the parts the move parameter can be used. It is string +with space separated words - at most one of each of those options:

+
    +
  • left / right

  • +
  • up / down

  • +
  • only

  • +
+

If “only” is given the part is not drawn but only the move is +done. This can be useful to go in one direction after having placed +multiple parts in the other and have returned with .ctx.restore().

+

For implementing parts the following helper function can be used to +implement a move parameter:

+
+
+Boxes.move(x, y, where, before=False, label='')[source]
+

Intended to be used by parts +where can be combinations of “up” or “down”, “left” or “right”, “only”, +“mirror” and “rotated” +when “only” is included the move is only done when before is True +“mirror” will flip the part along the y axis +“rotated” draws the parts rotated 90 counter clockwise +The function returns whether actual drawing of the part +should be ommited.

+
+
Parameters:
+
    +
  • x – width of part

  • +
  • y – height of part

  • +
  • where – which direction to move

  • +
  • before – (Default value = False) called before or after part being drawn

  • +
+
+
+
+ +

It needs to be called before and after drawing the actual part with +the proper before parameter set.

+
+
+

The edges parameter

+

The edges parameter needs to be an iterable of Edge instances to be +used as edges of the part. Instead of instances it is possible to pass +a single character that is looked up in the .edges dict. This +allows to pass a string with the desired characters per edge. By +default the following character are supported:

+
    +
  • e : straight edge

  • +
  • E : as above but extended outside by one thickness

  • +
  • f, F : finger joints

  • +
  • h : edge with holes for finger joints

  • +
  • d, D : dove tail joints

  • +
+

Generators can register their own Edges by putting them into the +.edges dictionary.

+

Same applies to the parameters of .surroundingWall although they +denominate single edge (types) only.

+
+
+

PartsMatrix

+

To place many of the same part partMatrix can used:

+
+
+Boxes.partsMatrix(n, width, move, part, *l, **kw)[source]
+

place many of the same part

+
+
Parameters:
+
    +
  • n – number of parts

  • +
  • width – number of parts in a row (0 for same as n)

  • +
  • move – (Default value = None)

  • +
  • part – callable that draws a part and knows move param

  • +
  • *l – params for part

  • +
  • **kw – keyword params for part

  • +
+
+
+
+ +

It creates one big block of parts. The move param treat this block like on big +part.

+
+
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/apidoc.html b/html/apidoc.html new file mode 100644 index 0000000..c0e48a6 --- /dev/null +++ b/html/apidoc.html @@ -0,0 +1,135 @@ + + + + + + + + + Using the Boxes.py API — boxes.py 0.1 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Using the Boxes.py API

+

If there is no generator fitting your needs you can either adjust an +existing one (may be by copying it to another name first) or writing a +new one from scratch.

+ +
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/boxes.html b/html/boxes.html new file mode 100644 index 0000000..aef2e12 --- /dev/null +++ b/html/boxes.html @@ -0,0 +1,4754 @@ + + + + + + + + + boxes package — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +
+

boxes package

+
+

Subpackage boxes.generators

+
+
+class boxes.generators.UIGroup(name, title=None, description='', image='')[source]
+

Bases: object

+
+
+add(box)[source]
+
+ +
+
+property image
+
+ +
+
+property thumbnail
+
+ +
+ +
+
+boxes.generators.getAllBoxGenerators()[source]
+
+ +
+
+boxes.generators.getAllGeneratorModules()[source]
+
+ +

All Box Generators

+
+
+

Submodules

+
+
+

boxes.Color module

+
+
+class boxes.Color.Color[source]
+

Bases: object

+
+
+ANNOTATIONS = [1.0, 0.0, 0.0]
+
+ +
+
+BLACK = [0.0, 0.0, 0.0]
+
+ +
+
+BLUE = [0.0, 0.0, 1.0]
+
+ +
+
+CYAN = [0.0, 1.0, 1.0]
+
+ +
+
+ETCHING = [0.0, 1.0, 0.0]
+
+ +
+
+ETCHING_DEEP = [0.0, 1.0, 1.0]
+
+ +
+
+GREEN = [0.0, 1.0, 0.0]
+
+ +
+
+INNER_CUT = [0.0, 0.0, 1.0]
+
+ +
+
+MAGENTA = [1.0, 0.0, 1.0]
+
+ +
+
+OUTER_CUT = [0.0, 0.0, 0.0]
+
+ +
+
+RED = [1.0, 0.0, 0.0]
+
+ +
+
+WHITE = [1.0, 1.0, 1.0]
+
+ +
+
+YELLOW = [1.0, 1.0, 0.0]
+
+ +
+ +
+
+

boxes.edges module

+
+
+class boxes.edges.BaseEdge(boxes, settings)[source]
+

Bases: object

+

Abstract base class for all Edges

+
+
+char = None
+
+ +
+
+description = 'Abstract Edge Class'
+
+ +
+
+endAngle()[source]
+

Not yet supported

+
+ +
+
+endwidth()[source]
+
+ +
+
+margin()[source]
+

Space needed right of the starting point

+
+ +
+
+spacing()[source]
+

Space the edge needs outside of the inner space of the part

+
+ +
+
+startAngle()[source]
+

Not yet supported

+
+ +
+
+startwidth()[source]
+

Amount of space the beginning of the edge is set below the inner space of the part

+
+ +
+ +
+
+class boxes.edges.BoltPolicy[source]
+

Bases: object

+

Abstract class

+

Distributes (bed) bolts on a number of segments +(fingers of a finger joint)

+
+
+drawbolt(pos)[source]
+

Add a bolt to this segment?

+
+
Parameters:
+

pos – number of the finger

+
+
+
+ +
+
+numFingers(numfingers)[source]
+

Return next smaller, possible number of fingers

+
+
Parameters:
+

numfingers – number of fingers to aim for

+
+
+
+ +
+ +
+
+class boxes.edges.Bolts(bolts=1)[source]
+

Bases: BoltPolicy

+

Distribute a fixed number of bolts evenly

+
+
+drawBolt(pos)[source]
+

Return if this finger needs a bolt

+
+
Parameters:
+

pos – number of this finger

+
+
+
+ +
+
+numFingers(numFingers)[source]
+

Return next smaller, possible number of fingers

+
+
Parameters:
+

numfingers – number of fingers to aim for

+
+
+
+ +
+ +
+
+class boxes.edges.CabinetHingeEdge(boxes, settings=None, top=False, angled=False)[source]
+

Bases: BaseEdge

+

Edge with cabinet hinges

+
+
+char = 'u'
+
+ +
+
+description = 'Edge with cabinet hinges'
+
+ +
+
+parts(move=None)[source]
+
+ +
+
+startwidth()[source]
+

Amount of space the beginning of the edge is set below the inner space of the part

+
+ +
+ +
+
+class boxes.edges.CabinetHingeSettings(thickness, relative=True, **kw)[source]
+

Bases: Settings

+

Settings for Cabinet Hinges +Values:

+
    +
  • absolute_params

  • +
+
+
    +
  • bore : 3.2 : diameter of the pin hole in mm

  • +
  • eyes_per_hinge : 5 : pieces per hinge

  • +
  • hinges : 2 : number of hinges per edge

  • +
  • style : inside : style of hinge used

  • +
+
+
    +
  • relative (in multiples of thickness)

  • +
+
+
    +
  • eye : 1.5 : radius of the eye (multiples of thickness)

  • +
  • play : 0.05 : space between eyes (multiples of thickness)

  • +
  • spacing : 2.0 : minimum space around the hinge (multiples of thickness)

  • +
+
+
+
+absolute_params = {'bore': 3.2, 'eyes_per_hinge': 5, 'hinges': 2, 'style': ('inside', 'outside')}
+
+ +
+
+edgeObjects(boxes, chars='uUvV', add=True)[source]
+

Generate Edge objects using this kind of settings

+
+
Parameters:
+
    +
  • boxes – Boxes object

  • +
  • chars – sequence of chars to be used by Edge objects

  • +
  • add – add the resulting Edge objects to the Boxes object’s edges

  • +
+
+
+
+ +
+
+relative_params = {'eye': 1.5, 'play': 0.05, 'spacing': 2.0}
+
+ +
+ +
+
+class boxes.edges.ChestHinge(boxes, settings=None, reversed=False)[source]
+

Bases: BaseEdge

+
+
+char = 'o'
+
+ +
+
+description = 'Edge with chest hinge'
+
+ +
+
+endwidth()[source]
+
+ +
+
+margin()[source]
+

Space needed right of the starting point

+
+ +
+
+startwidth()[source]
+

Amount of space the beginning of the edge is set below the inner space of the part

+
+ +
+ +
+
+class boxes.edges.ChestHingeFront(boxes, settings)[source]
+

Bases: Edge

+
+
+char = 'Q'
+
+ +
+
+description = 'Edge opposing a chest hinge'
+
+ +
+
+startwidth()[source]
+

Amount of space the beginning of the edge is set below the inner space of the part

+
+ +
+ +
+
+class boxes.edges.ChestHingePin(boxes, settings)[source]
+

Bases: BaseEdge

+
+
+char = 'q'
+
+ +
+
+description = 'Edge with pins for an chest hinge'
+
+ +
+
+margin()[source]
+

Space needed right of the starting point

+
+ +
+ +
+
+class boxes.edges.ChestHingeSettings(thickness, relative=True, **kw)[source]
+

Bases: Settings

+

Settings for Chest Hinges +Values:

+
    +
  • relative (in multiples of thickness)

  • +
+
+
    +
  • pin_height : 2.0 : radius of the disc rotating in the hinge (multiples of thickness)

  • +
  • hinge_strength : 1.0 : thickness of the arc holding the pin in place (multiples of thickness)

  • +
+
+
    +
  • absolute

  • +
+
+
    +
  • finger_joints_on_box : False : whether to include finger joints on the edge with the box

  • +
  • finger_joints_on_lid : False : whether to include finger joints on the edge with the lid

  • +
+
+
+
+absolute_params = {'finger_joints_on_box': False, 'finger_joints_on_lid': False}
+
+ +
+
+checkValues()[source]
+

Check if all values are in the right range. Raise ValueError if needed

+
+ +
+
+edgeObjects(boxes, chars='oOpPqQ', add=True)[source]
+

Generate Edge objects using this kind of settings

+
+
Parameters:
+
    +
  • boxes – Boxes object

  • +
  • chars – sequence of chars to be used by Edge objects

  • +
  • add – add the resulting Edge objects to the Boxes object’s edges

  • +
+
+
+
+ +
+
+pinheight()[source]
+
+ +
+
+relative_params = {'hinge_strength': 1.0, 'pin_height': 2.0, 'play': 0.1}
+
+ +
+ +
+
+class boxes.edges.ChestHingeTop(boxes, settings=None, reversed=False)[source]
+

Bases: ChestHinge

+

Edge above a chest hinge

+
+
+char = 'p'
+
+ +
+
+endwidth()[source]
+
+ +
+
+margin()[source]
+

Space needed right of the starting point

+
+ +
+
+startwidth()[source]
+

Amount of space the beginning of the edge is set below the inner space of the part

+
+ +
+ +
+
+class boxes.edges.ClickConnector(boxes, settings)[source]
+

Bases: BaseEdge

+
+
+char = 'c'
+
+ +
+
+description = 'Click on (bottom side)'
+
+ +
+
+finger(length)[source]
+
+ +
+
+hook(reverse=False)[source]
+
+ +
+
+hookOffset()[source]
+
+ +
+
+hookWidth()[source]
+
+ +
+
+margin()[source]
+

Space needed right of the starting point

+
+ +
+ +
+
+class boxes.edges.ClickEdge(boxes, settings)[source]
+

Bases: ClickConnector

+
+
+char = 'C'
+
+ +
+
+description = 'Click on (top)'
+
+ +
+
+margin()[source]
+

Space needed right of the starting point

+
+ +
+
+startwidth()[source]
+

Amount of space the beginning of the edge is set below the inner space of the part

+
+ +
+ +
+
+class boxes.edges.ClickSettings(thickness, relative=True, **kw)[source]
+

Bases: Settings

+

Settings for Click-on Lids +Values:

+
    +
  • absolute_params

    +
      +
    • angle : 5.0 : angle of the hooks bending outward

    • +
    +
  • +
  • relative (in multiples of thickness)

    +
      +
    • depth : 3.0 : length of the hooks (multiples of thickness)

    • +
    • bottom_radius : 0.1 : radius at the bottom (multiples of thickness)

    • +
    +
  • +
+
+
+absolute_params = {'angle': 5.0}
+
+ +
+
+edgeObjects(boxes, chars='cC', add=True)[source]
+

Generate Edge objects using this kind of settings

+
+
Parameters:
+
    +
  • boxes – Boxes object

  • +
  • chars – sequence of chars to be used by Edge objects

  • +
  • add – add the resulting Edge objects to the Boxes object’s edges

  • +
+
+
+
+ +
+
+relative_params = {'bottom_radius': 0.1, 'depth': 3.0}
+
+ +
+ +
+
+class boxes.edges.CompoundEdge(boxes, types, lengths)[source]
+

Bases: BaseEdge

+

Edge composed of multiple different Edges

+
+
+description = 'Compound Edge'
+
+ +
+
+endwidth()[source]
+
+ +
+
+margin()[source]
+

Space needed right of the starting point

+
+ +
+
+startwidth()[source]
+

Amount of space the beginning of the edge is set below the inner space of the part

+
+ +
+ +
+
+class boxes.edges.CrossingFingerHoleEdge(boxes, height, fingerHoles=None, **kw)[source]
+

Bases: Edge

+

Edge with holes for finger joints 90° above

+
+
+char = '|'
+
+ +
+
+description = 'Edge (orthogonal Finger Joint Holes)'
+
+ +
+ +
+
+class boxes.edges.DoveTailJoint(boxes, settings)[source]
+

Bases: BaseEdge

+

Edge with dove tail joints

+
+
+char = 'd'
+
+ +
+
+description = 'Dove Tail Joint'
+
+ +
+
+margin()[source]
+
+ +
+
+positive = True
+
+ +
+ +
+
+class boxes.edges.DoveTailJointCounterPart(boxes, settings)[source]
+

Bases: DoveTailJoint

+

Edge for other side of dove joints

+
+
+char = 'D'
+
+ +
+
+description = 'Dove Tail Joint (opposing side)'
+
+ +
+
+margin()[source]
+
+ +
+
+positive = False
+
+ +
+ +
+
+class boxes.edges.DoveTailSettings(thickness, relative=True, **kw)[source]
+

Bases: Settings

+

Settings for Dove Tail Joints

+

Values:

+
    +
  • absolute

    +
      +
    • angle : 50 : how much should fingers widen (-80 to 80)

    • +
    +
  • +
  • relative (in multiples of thickness)

    +
      +
    • size : 3 : from one middle of a dove tail to another (multiples of thickness)

    • +
    • depth : 1.5 : how far the dove tails stick out of/into the edge (multiples of thickness)

    • +
    • radius : 0.2 : radius used on all four corners (multiples of thickness)

    • +
    +
  • +
+
+
+absolute_params = {'angle': 50}
+
+ +
+
+edgeObjects(boxes, chars='dD', add=True)[source]
+

Generate Edge objects using this kind of settings

+
+
Parameters:
+
    +
  • boxes – Boxes object

  • +
  • chars – sequence of chars to be used by Edge objects

  • +
  • add – add the resulting Edge objects to the Boxes object’s edges

  • +
+
+
+
+ +
+
+relative_params = {'depth': 1.5, 'radius': 0.2, 'size': 3}
+
+ +
+ +
+
+class boxes.edges.Edge(boxes, settings)[source]
+

Bases: BaseEdge

+

Straight edge

+
+
+char = 'e'
+
+ +
+
+description = 'Straight Edge'
+
+ +
+
+positive = False
+
+ +
+ +
+
+class boxes.edges.FingerHoleEdge(boxes, fingerHoles=None, **kw)[source]
+

Bases: BaseEdge

+

Edge with holes for a parallel finger joint

+
+
+char = 'h'
+
+ +
+
+description = 'Edge (parallel Finger Joint Holes)'
+
+ +
+
+startwidth()[source]
+
+ +
+ +
+
+class boxes.edges.FingerHoles(boxes, settings)[source]
+

Bases: FingerJointBase

+

Hole matching a finger joint edge

+
+ +
+
+class boxes.edges.FingerJointBase[source]
+

Bases: object

+
+
+calcFingers(length, bedBolts)[source]
+
+ +
+
+fingerLength(angle)[source]
+
+ +
+ +
+
+class boxes.edges.FingerJointEdge(boxes, settings)[source]
+

Bases: BaseEdge, FingerJointBase

+

Finger joint edge

+
+
+char = 'f'
+
+ +
+
+description = 'Finger Joint'
+
+ +
+
+draw_finger(f, h, style, positive=True, firsthalf=True)[source]
+
+ +
+
+margin()[source]
+
+ +
+
+positive = True
+
+ +
+
+startwidth()[source]
+

Amount of space the beginning of the edge is set below the inner space of the part

+
+ +
+ +
+
+class boxes.edges.FingerJointEdgeCounterPart(boxes, settings)[source]
+

Bases: FingerJointEdge

+

Finger joint edge - other side

+
+
+char = 'F'
+
+ +
+
+description = 'Finger Joint (opposing side)'
+
+ +
+
+positive = False
+
+ +
+ +
+
+class boxes.edges.FingerJointSettings(thickness, relative=True, **kw)[source]
+

Bases: Settings

+

Settings for Finger Joints

+

Values:

+
    +
  • absolute +* style : “rectangular” : style of the fingers +* surroundingspaces : 2.0 : space at the start and end in multiple of normal spaces +* angle: 90 : Angle of the walls meeting

  • +
  • relative (in multiples of thickness)

    +
      +
    • space : 2.0 : space between fingers (multiples of thickness)

    • +
    • finger : 2.0 : width of the fingers (multiples of thickness)

    • +
    • width : 1.0 : width of finger holes (multiples of thickness)

    • +
    • edge_width : 1.0 : space below holes of FingerHoleEdge (multiples of thickness)

    • +
    • play : 0.0 : extra space to allow finger move in and out (multiples of thickness)

    • +
    • extra_length : 0.0 : extra material to grind away burn marks (multiples of thickness)

    • +
    +
  • +
+
+
+absolute_params = {'angle': 90.0, 'style': ('rectangular', 'springs', 'barbs', 'snap'), 'surroundingspaces': 2.0}
+
+ +
+
+checkValues()[source]
+

Check if all values are in the right range. Raise ValueError if needed

+
+ +
+
+edgeObjects(boxes, chars='fFh', add=True)[source]
+

Generate Edge objects using this kind of settings

+
+
Parameters:
+
    +
  • boxes – Boxes object

  • +
  • chars – sequence of chars to be used by Edge objects

  • +
  • add – add the resulting Edge objects to the Boxes object’s edges

  • +
+
+
+
+ +
+
+relative_params = {'edge_width': 1.0, 'extra_length': 0.0, 'finger': 2.0, 'play': 0.0, 'space': 2.0, 'width': 1.0}
+
+ +
+ +
+
+class boxes.edges.FlexEdge(boxes, settings)[source]
+

Bases: BaseEdge

+

Edge with flex cuts - use straight edge for the opposing side

+
+
+char = 'X'
+
+ +
+
+description = 'Flex cut'
+
+ +
+ +
+
+class boxes.edges.FlexSettings(thickness, relative=True, **kw)[source]
+

Bases: Settings

+

Settings for Flex

+

Values:

+
    +
  • absolute

  • +
+
+
    +
  • stretch : 1.05 : Hint of how much the flex part should be shortend

  • +
+
+
    +
  • relative (in multiples of thickness)

  • +
+
+
    +
  • distance : 0.5 : width of the pattern perpendicular to the cuts (multiples of thickness)

  • +
  • connection : 1.0 : width of the gaps in the cuts (multiples of thickness)

  • +
  • width : 5.0 : width of the pattern in direction of the cuts (multiples of thickness)

  • +
+
+
+
+absolute_params = {'stretch': 1.05}
+
+ +
+
+checkValues()[source]
+

Check if all values are in the right range. Raise ValueError if needed

+
+ +
+
+relative_params = {'connection': 1.0, 'distance': 0.5, 'width': 5.0}
+
+ +
+ +
+
+class boxes.edges.GearSettings(thickness, relative=True, **kw)[source]
+

Bases: Settings

+

Settings for rack (and pinion) edge +Values: +* absolute_params

+
+
    +
  • dimension : 3.0 : modulus of the gear (in mm)

  • +
  • angle : 20.0 : pressure angle

  • +
  • profile_shift : 20.0 : Profile shift

  • +
  • clearance : 0.0 : clearance

  • +
+
+
+
+absolute_params = {'angle': 20.0, 'clearance': 0.0, 'dimension': 3.0, 'profile_shift': 20.0}
+
+ +
+
+relative_params = {}
+
+ +
+ +
+
+class boxes.edges.GripSettings(thickness, relative=True, **kw)[source]
+

Bases: Settings

+

Settings for GrippingEdge +Values:

+
    +
  • absolute_params

  • +
+
+
    +
  • style : “wave : “wave” or “bumps”

  • +
  • outset : True : extend outward the straight edge

  • +
+
+
    +
  • relative (in multiples of thickness)

  • +
+
+
    +
  • depth : 0.3 : depth of the grooves

  • +
+
+
+
+absolute_params = {'outset': True, 'style': ('wave', 'bumps')}
+
+ +
+
+edgeObjects(boxes, chars='g', add=True)[source]
+

Generate Edge objects using this kind of settings

+
+
Parameters:
+
    +
  • boxes – Boxes object

  • +
  • chars – sequence of chars to be used by Edge objects

  • +
  • add – add the resulting Edge objects to the Boxes object’s edges

  • +
+
+
+
+ +
+
+relative_params = {'depth': 0.3}
+
+ +
+ +
+
+class boxes.edges.GrippingEdge(boxes, settings)[source]
+

Bases: BaseEdge

+
+
+bumps(length)[source]
+
+ +
+
+char = 'g'
+
+ +
+
+description = 'Corrugated edge useful as an gipping area'
+
+ +
+
+margin()[source]
+

Space needed right of the starting point

+
+ +
+
+wave(length)[source]
+
+ +
+ +
+
+class boxes.edges.GroovedEdge(boxes, settings)[source]
+

Bases: GroovedEdgeBase

+
+
+char = 'z'
+
+ +
+
+description = 'Edge with grooves'
+
+ +
+
+inverse = False
+
+ +
+ +
+
+class boxes.edges.GroovedEdgeBase(boxes, settings)[source]
+

Bases: BaseEdge

+
+
+groove_arc(width, angle=90, inv=-1.0)[source]
+
+ +
+
+groove_soft_arc(width, angle=60, inv=-1.0)[source]
+
+ +
+
+groove_triangle(width, angle=45, inv=-1.0)[source]
+
+ +
+
+is_inverse()[source]
+
+ +
+ +
+
+class boxes.edges.GroovedEdgeCounterPart(boxes, settings)[source]
+

Bases: GroovedEdgeBase

+
+
+char = 'Z'
+
+ +
+
+description = 'Edge with grooves (opposing side)'
+
+ +
+
+inverse = True
+
+ +
+ +
+
+class boxes.edges.GroovedSettings(thickness, relative=True, **kw)[source]
+

Bases: Settings

+

Settings for Grooved Edge +Values:

+
    +
  • absolute_params

  • +
+
+
    +
  • style : “arc” : the style of grooves

  • +
  • tri_angle : 30 : the angle of triangular cuts

  • +
  • arc_angle : 120 : the angle of arc cuts

  • +
  • width : 0.2 : the width of each groove (fraction of the edge length)

  • +
  • gap : 0.1 : the gap between grooves (fraction of the edge length)

  • +
  • margin : 0.3 : minimum space left and right without grooves (fraction of the edge length)

  • +
  • inverse : False : invert the groove directions

  • +
  • interleave : False : alternate the direction of grooves

  • +
+
+
+
+PARAM_ARC = 'arc'
+
+ +
+
+PARAM_FLAT = 'flat'
+
+ +
+
+PARAM_SOFTARC = 'softarc'
+
+ +
+
+PARAM_TRIANGLE = 'triangle'
+
+ +
+
+absolute_params = {'arc_angle': 120, 'gap': 0.1, 'interleave': False, 'inverse': False, 'margin': 0.3, 'style': ('arc', 'flat', 'triangle', 'softarc'), 'tri_angle': 30, 'width': 0.2}
+
+ +
+
+edgeObjects(boxes, chars='zZ', add=True)[source]
+

Generate Edge objects using this kind of settings

+
+
Parameters:
+
    +
  • boxes – Boxes object

  • +
  • chars – sequence of chars to be used by Edge objects

  • +
  • add – add the resulting Edge objects to the Boxes object’s edges

  • +
+
+
+
+ +
+ +
+
+class boxes.edges.HandleEdge(boxes, settings)[source]
+

Bases: Edge

+

Extends an ‘edge’ by adding a rounded bumpout with optional holes

+
+
+char = 'y'
+
+ +
+
+description = 'Handle for e.g. a drawer'
+
+ +
+
+extra_height = 0.0
+
+ +
+
+margin()[source]
+

Space needed right of the starting point

+
+ +
+ +
+
+class boxes.edges.HandleEdgeSettings(thickness, relative=True, **kw)[source]
+

Bases: Settings

+

Settings for HandleEdge +Values:

+
    +
  • absolute_params

  • +
+
+
    +
  • height : 20. : height above the wall in mm

  • +
  • radius : 10. : radius of corners in mm

  • +
  • hole_width : “40:40” : width of hole(s) in percentage of maximum hole width (width of edge - (n+1) * material thickness)

  • +
  • hole_height : 75. : height of hole(s) in percentage of maximum hole height (handle height - 2 * material thickness)

  • +
  • on_sides : True, : added to side panels if checked, to front and back otherwise (only used with top_edge parameter)

  • +
+
+
    +
  • relative

  • +
+
+
    +
  • outset : 1. : extend the handle along the length of the edge (multiples of thickness)

  • +
+
+
+
+absolute_params = {'height': 20.0, 'hole_height': 75.0, 'hole_width': '40:40', 'on_sides': True, 'radius': 10.0}
+
+ +
+
+edgeObjects(boxes, chars='yY', add=True)[source]
+

Generate Edge objects using this kind of settings

+
+
Parameters:
+
    +
  • boxes – Boxes object

  • +
  • chars – sequence of chars to be used by Edge objects

  • +
  • add – add the resulting Edge objects to the Boxes object’s edges

  • +
+
+
+
+ +
+
+relative_params = {'outset': 1.0}
+
+ +
+ +
+
+class boxes.edges.HandleHoleEdge(boxes, settings)[source]
+

Bases: HandleEdge

+

Extends an ‘edge’ by adding a rounded bumpout with optional holes and holes for parallel finger joint

+
+
+char = 'Y'
+
+ +
+
+description = 'Handle with holes for parallel finger joint'
+
+ +
+
+extra_height = 1.0
+
+ +
+
+margin()[source]
+

Space needed right of the starting point

+
+ +
+ +
+
+class boxes.edges.Hinge(boxes, settings=None, layout=1)[source]
+

Bases: BaseEdge

+
+
+char = 'i'
+
+ +
+
+description = 'Straight edge with hinge eye'
+
+ +
+
+flush(_reversed=False)[source]
+
+ +
+
+flushlen()[source]
+
+ +
+
+margin()[source]
+

Space needed right of the starting point

+
+ +
+
+outset(_reversed=False)[source]
+
+ +
+
+outsetlen()[source]
+
+ +
+ +
+
+class boxes.edges.HingePin(boxes, settings=None, layout=1)[source]
+

Bases: BaseEdge

+
+
+char = 'I'
+
+ +
+
+description = 'Edge with hinge pin'
+
+ +
+
+endwidth()[source]
+
+ +
+
+flush(_reversed=False)[source]
+
+ +
+
+flushlen()[source]
+
+ +
+
+margin()[source]
+

Space needed right of the starting point

+
+ +
+
+outset(_reversed=False)[source]
+
+ +
+
+outsetlen()[source]
+
+ +
+
+startwidth()[source]
+

Amount of space the beginning of the edge is set below the inner space of the part

+
+ +
+ +
+
+class boxes.edges.HingeSettings(thickness, relative=True, **kw)[source]
+

Bases: Settings

+

Settings for Hinges and HingePins +Values:

+
    +
  • absolute_params

  • +
+
+
    +
  • style : “outset” : “outset” or “flush”

  • +
  • outset : False : have lid overlap at the sides (similar to OutSetEdge)

  • +
  • pinwidth : 1.0 : set to lower value to get disks surrounding the pins

  • +
  • grip_percentage” : 0 : percentage of the lid that should get grips

  • +
+
+
    +
  • relative (in multiples of thickness)

  • +
+
+
    +
  • hingestrength : 1 : thickness of the arc holding the pin in place (multiples of thickness)

  • +
  • axle : 2 : diameter of the pin hole (multiples of thickness)

  • +
  • grip_length : 0 : fixed length of the grips on he lids (multiples of thickness)

  • +
+
+
+
+absolute_params = {'grip_percentage': 0, 'outset': False, 'pinwidth': 0.5, 'style': ('outset', 'flush')}
+
+ +
+
+checkValues()[source]
+

Check if all values are in the right range. Raise ValueError if needed

+
+ +
+
+edgeObjects(boxes, chars='iIjJkK', add=True)[source]
+

Generate Edge objects using this kind of settings

+
+
Parameters:
+
    +
  • boxes – Boxes object

  • +
  • chars – sequence of chars to be used by Edge objects

  • +
  • add – add the resulting Edge objects to the Boxes object’s edges

  • +
+
+
+
+ +
+
+relative_params = {'axle': 2.0, 'grip_length': 0, 'hingestrength': 1}
+
+ +
+ +
+
+class boxes.edges.LidEdge(boxes, settings)[source]
+

Bases: FingerJointEdge

+
+
+char = 'l'
+
+ +
+
+description = 'Edge for slide on lid (back)'
+
+ +
+ +
+
+class boxes.edges.LidHoleEdge(boxes, fingerHoles=None, **kw)[source]
+

Bases: FingerHoleEdge

+
+
+char = 'L'
+
+ +
+
+description = 'Edge for slide on lid (box back)'
+
+ +
+ +
+
+class boxes.edges.LidLeft(boxes, settings)[source]
+

Bases: LidRight

+
+
+char = 'm'
+
+ +
+
+description = 'Edge for slide on lid (left)'
+
+ +
+
+rightside = False
+
+ +
+ +
+
+class boxes.edges.LidRight(boxes, settings)[source]
+

Bases: BaseEdge

+
+
+char = 'n'
+
+ +
+
+description = 'Edge for slide on lid (right)'
+
+ +
+
+endwidth()[source]
+
+ +
+
+margin()[source]
+

Space needed right of the starting point

+
+ +
+
+rightside = True
+
+ +
+
+startwidth()[source]
+

Amount of space the beginning of the edge is set below the inner space of the part

+
+ +
+ +
+
+class boxes.edges.LidSettings(thickness, relative=True, **kw)[source]
+

Bases: FingerJointSettings

+

Settings for Slide-on Lids

+

Note that edge_width below also determines how much the sides extend above the lid.

+

Values:

+
    +
  • absolute_params

  • +
+
+
    +
  • second_pin : True : additional pin for better positioning

  • +
  • spring : “both” : position(s) of the extra locking springs in the lid

  • +
  • hole_width : 0 : width of the “finger hole” in mm

    +
    +

    Settings for Finger Joints

    +
    +
  • +
+
+

Values:

+
    +
  • absolute +* style : “rectangular” : style of the fingers +* surroundingspaces : 2.0 : space at the start and end in multiple of normal spaces +* angle: 90 : Angle of the walls meeting

  • +
  • relative (in multiples of thickness)

    +
      +
    • space : 2.0 : space between fingers (multiples of thickness)

    • +
    • finger : 2.0 : width of the fingers (multiples of thickness)

    • +
    • width : 1.0 : width of finger holes (multiples of thickness)

    • +
    • edge_width : 1.0 : space below holes of FingerHoleEdge (multiples of thickness)

    • +
    • play : 0.0 : extra space to allow finger move in and out (multiples of thickness)

    • +
    • extra_length : 0.0 : extra material to grind away burn marks (multiples of thickness)

    • +
    +
  • +
+
+
+absolute_params = {'angle': 90.0, 'hole_width': 0, 'second_pin': True, 'spring': ('both', 'none', 'left', 'right'), 'style': ('rectangular', 'springs', 'barbs', 'snap'), 'surroundingspaces': 2.0}
+
+ +
+
+edgeObjects(boxes, chars=None, add=True)[source]
+

Generate Edge objects using this kind of settings

+
+
Parameters:
+
    +
  • boxes – Boxes object

  • +
  • chars – sequence of chars to be used by Edge objects

  • +
  • add – add the resulting Edge objects to the Boxes object’s edges

  • +
+
+
+
+ +
+
+relative_params = {'edge_width': 1.0, 'extra_length': 0.0, 'finger': 3.0, 'play': 0.05, 'space': 2.0, 'width': 1.0}
+
+ +
+ +
+
+class boxes.edges.LidSideLeft(boxes, settings)[source]
+

Bases: LidSideRight

+
+
+char = 'M'
+
+ +
+
+description = 'Edge for slide on lid (box left)'
+
+ +
+
+rightside = False
+
+ +
+ +
+
+class boxes.edges.LidSideRight(boxes, settings)[source]
+

Bases: BaseEdge

+
+
+char = 'N'
+
+ +
+
+description = 'Edge for slide on lid (box right)'
+
+ +
+
+endwidth()[source]
+
+ +
+
+margin()[source]
+

Space needed right of the starting point

+
+ +
+
+rightside = True
+
+ +
+
+startwidth()[source]
+

Amount of space the beginning of the edge is set below the inner space of the part

+
+ +
+ +
+
+class boxes.edges.MountingEdge(boxes, settings)[source]
+

Bases: BaseEdge

+
+
+char = 'G'
+
+ +
+
+description = 'Edge with pear shaped mounting holes'
+
+ +
+
+margin()[source]
+

Space needed right of the starting point

+
+ +
+
+startwidth()[source]
+

Amount of space the beginning of the edge is set below the inner space of the part

+
+ +
+ +
+
+class boxes.edges.MountingSettings(thickness, relative=True, **kw)[source]
+

Bases: Settings

+

Settings for Mounting Edge +Values: +* absolute_params

+
+
    +
  • style : “straight edge, within” : edge style

  • +
  • side : “back” : side of box (not all valid configurations make sense…)

  • +
  • num : 2 : number of mounting holes (integer)

  • +
  • margin : 0.125 : minimum space left and right without holes (fraction of the edge length)

  • +
  • d_shaft : 3.0 : shaft diameter of mounting screw (in mm)

  • +
  • d_head : 6.5 : head diameter of mounting screw (in mm)

  • +
+
+
+
+PARAM_BACK = 'back'
+
+ +
+
+PARAM_EXT = 'straight edge, extended'
+
+ +
+
+PARAM_FRONT = 'front'
+
+ +
+
+PARAM_IN = 'straight edge, within'
+
+ +
+
+PARAM_LEFT = 'left'
+
+ +
+
+PARAM_RIGHT = 'right'
+
+ +
+
+PARAM_TAB = 'mounting tab'
+
+ +
+
+absolute_params = {'d_head': 6.5, 'd_shaft': 3.0, 'margin': 0.125, 'num': 2, 'side': ('back', 'left', 'right', 'front'), 'style': ('straight edge, within', 'straight edge, extended', 'mounting tab')}
+
+ +
+
+edgeObjects(boxes, chars='G', add=True)[source]
+

Generate Edge objects using this kind of settings

+
+
Parameters:
+
    +
  • boxes – Boxes object

  • +
  • chars – sequence of chars to be used by Edge objects

  • +
  • add – add the resulting Edge objects to the Boxes object’s edges

  • +
+
+
+
+ +
+ +
+
+class boxes.edges.OutSetEdge(boxes, settings)[source]
+

Bases: Edge

+

Straight edge out set by one thickness

+
+
+char = 'E'
+
+ +
+
+description = 'Straight Edge (outset by thickness)'
+
+ +
+
+positive = True
+
+ +
+
+startwidth()[source]
+

Amount of space the beginning of the edge is set below the inner space of the part

+
+ +
+ +
+
+class boxes.edges.RackEdge(boxes, settings)[source]
+

Bases: BaseEdge

+
+
+char = 'R'
+
+ +
+
+description = 'Rack (and pinion) Edge'
+
+ +
+
+margin()[source]
+

Space needed right of the starting point

+
+ +
+ +
+
+class boxes.edges.RoundedTriangleEdge(boxes, settings)[source]
+

Bases: Edge

+

Makes an ‘edge’ with a rounded triangular bumpout and +optional hole

+
+
+char = 't'
+
+ +
+
+description = 'Triangle for handle'
+
+ +
+
+margin()[source]
+

Space needed right of the starting point

+
+ +
+ +
+
+class boxes.edges.RoundedTriangleEdgeSettings(thickness, relative=True, **kw)[source]
+

Bases: Settings

+

Settings for RoundedTriangleEdge +Values:

+
    +
  • absolute_params

  • +
+
+
    +
  • height : 150. : height above the wall

  • +
  • radius : 30. : radius of top corner

  • +
  • r_hole : 0. : radius of hole

  • +
+
+
    +
  • relative (in multiples of thickness)

  • +
+
+
    +
  • outset : 0 : extend the triangle along the length of the edge (multiples of thickness)

  • +
+
+
+
+absolute_params = {'height': 50.0, 'r_hole': 2.0, 'radius': 30.0}
+
+ +
+
+edgeObjects(boxes, chars='t', add=True)[source]
+

Generate Edge objects using this kind of settings

+
+
Parameters:
+
    +
  • boxes – Boxes object

  • +
  • chars – sequence of chars to be used by Edge objects

  • +
  • add – add the resulting Edge objects to the Boxes object’s edges

  • +
+
+
+
+ +
+
+relative_params = {'outset': 0.0}
+
+ +
+ +
+
+class boxes.edges.RoundedTriangleFingerHolesEdge(boxes, settings)[source]
+

Bases: RoundedTriangleEdge

+
+
+char = 'T'
+
+ +
+
+startwidth()[source]
+

Amount of space the beginning of the edge is set below the inner space of the part

+
+ +
+ +
+
+class boxes.edges.Settings(thickness, relative=True, **kw)[source]
+

Bases: object

+

Generic Settings class

+

Used by different other classes to store measurements and details. +Supports absolute values and settings that grow with the thickness +of the material used.

+

Overload the absolute_params and relative_params class attributes with +the suported keys and default values. The values are available via +attribute access.

+
+
+absolute_params = {}
+
+ +
+
+checkValues()[source]
+

Check if all values are in the right range. Raise ValueError if needed

+
+ +
+
+edgeObjects(boxes, chars='', add=True)[source]
+

Generate Edge objects using this kind of settings

+
+
Parameters:
+
    +
  • boxes – Boxes object

  • +
  • chars – sequence of chars to be used by Edge objects

  • +
  • add – add the resulting Edge objects to the Boxes object’s edges

  • +
+
+
+
+ +
+
+classmethod parserArguments(parser, prefix=None, **defaults)[source]
+
+ +
+
+relative_params = {}
+
+ +
+
+setValues(thickness, relative=True, **kw)[source]
+

Set values

+
+
Parameters:
+
    +
  • thickness – thickness of the material used

  • +
  • relative – (Default value = True) Do scale by thickness

  • +
  • **kw – parameters to set

  • +
+
+
+
+ +
+ +
+
+class boxes.edges.Slot(boxes, depth)[source]
+

Bases: BaseEdge

+

Edge with an slot to slid another pice through

+
+
+description = 'Slot'
+
+ +
+ +
+
+class boxes.edges.SlottedEdge(boxes, sections, edge='e', slots=0)[source]
+

Bases: BaseEdge

+

Edge with multiple slots

+
+
+description = 'Straight Edge with slots'
+
+ +
+
+endwidth()[source]
+
+ +
+
+margin()[source]
+

Space needed right of the starting point

+
+ +
+
+startwidth()[source]
+

Amount of space the beginning of the edge is set below the inner space of the part

+
+ +
+ +
+
+class boxes.edges.StackableBaseEdge(boxes, settings, fingerjointsettings)[source]
+

Bases: BaseEdge

+

Edge for having stackable Boxes. The Edge creates feet on the bottom +and has matching recesses on the top corners.

+
+
+bottom = True
+
+ +
+
+char = 's'
+
+ +
+
+description = 'Abstract Stackable class'
+
+ +
+
+margin()[source]
+

Space needed right of the starting point

+
+ +
+
+startwidth()[source]
+

Amount of space the beginning of the edge is set below the inner space of the part

+
+ +
+ +
+
+class boxes.edges.StackableEdge(boxes, settings, fingerjointsettings)[source]
+

Bases: StackableBaseEdge

+

Edge for having stackable Boxes. The Edge creates feet on the bottom +and has matching recesses on the top corners.

+
+
+char = 's'
+
+ +
+
+description = 'Stackable (bottom, finger joint holes)'
+
+ +
+ +
+
+class boxes.edges.StackableEdgeTop(boxes, settings, fingerjointsettings)[source]
+

Bases: StackableBaseEdge

+
+
+bottom = False
+
+ +
+
+char = 'S'
+
+ +
+
+description = 'Stackable (top)'
+
+ +
+ +
+
+class boxes.edges.StackableFeet(boxes, settings, fingerjointsettings)[source]
+

Bases: StackableBaseEdge

+
+
+char = 'š'
+
+ +
+
+description = 'Stackable feet (bottom)'
+
+ +
+ +
+
+class boxes.edges.StackableHoleEdgeTop(boxes, settings, fingerjointsettings)[source]
+

Bases: StackableBaseEdge

+
+
+bottom = False
+
+ +
+
+char = 'Š'
+
+ +
+
+description = 'Stackable edge with finger holes (top)'
+
+ +
+
+startwidth()[source]
+

Amount of space the beginning of the edge is set below the inner space of the part

+
+ +
+ +
+
+class boxes.edges.StackableSettings(thickness, relative=True, **kw)[source]
+

Bases: Settings

+

Settings for Stackable Edges

+

Values:

+
    +
  • absolute_params

    +
      +
    • angle : 60 : inside angle of the feet

    • +
    +
  • +
  • relative (in multiples of thickness)

    +
      +
    • height : 2.0 : height of the feet (multiples of thickness)

    • +
    • width : 4.0 : width of the feet (multiples of thickness)

    • +
    • holedistance : 1.0 : distance from finger holes to bottom edge (multiples of thickness)

    • +
    +
  • +
+
+
+absolute_params = {'angle': 60}
+
+ +
+
+checkValues()[source]
+

Check if all values are in the right range. Raise ValueError if needed

+
+ +
+
+edgeObjects(boxes, chars='sSšŠ', add=True, fingersettings=None)[source]
+

Generate Edge objects using this kind of settings

+
+
Parameters:
+
    +
  • boxes – Boxes object

  • +
  • chars – sequence of chars to be used by Edge objects

  • +
  • add – add the resulting Edge objects to the Boxes object’s edges

  • +
+
+
+
+ +
+
+relative_params = {'height': 2.0, 'holedistance': 1.0, 'width': 4.0}
+
+ +
+ +
+
+boxes.edges.argparseSections(s)[source]
+

Parse sections parameter

+
+
Parameters:
+

s – string to parse

+
+
+
+ +
+
+boxes.edges.getDescriptions()[source]
+
+ +
+
+

boxes.formats module

+
+
+class boxes.formats.Formats[source]
+

Bases: object

+
+
+convert(filename, fmt, metadata=None)[source]
+
+ +
+
+formats = {'ai': ['-f', 'ps2ai'], 'dxf': ['-flat', '0.1', '-f', 'dxf:-mm'], 'gcode': ['-f', 'gcode'], 'lbrn2': None, 'pdf': ['-f', 'pdf'], 'plt': ['-f', 'plot-hpgl'], 'ps': None, 'svg': None, 'svg_Ponoko': None}
+
+ +
+
+getFormats()[source]
+
+ +
+
+getSurface(fmt, filename)[source]
+
+ +
+
+http_headers = {'dxf': [('Content-type', 'image/vnd.dxf')], 'gcode': [('Content-type', 'text/plain; charset=utf-8')], 'lbrn2': [('Content-type', 'application/lbrn2')], 'plt': [('Content-type', ' application/vnd.hp-hpgl')], 'ps': [('Content-type', 'application/postscript')], 'svg': [('Content-type', 'image/svg+xml; charset=utf-8')], 'svg_Ponoko': [('Content-type', 'image/svg+xml; charset=utf-8')]}
+
+ +
+
+pstoedit_candidates = ['/usr/bin/pstoedit', 'pstoedit', 'pstoedit.exe']
+
+ +
+ +
+
+

boxes.gears module

+
+
+class boxes.gears.Gears(boxes, **kw)[source]
+

Bases: object

+
+
+calc_circular_pitch()[source]
+

We use math based on circular pitch.

+
+ +
+
+drawPoints(lines, kerfdir=1, close=True)[source]
+
+ +
+
+gearCarrier(r, spoke_width, positions, mount_radius, mount_hole, circle=True, callback=None, move=None)[source]
+
+ +
+
+generate_spokes(root_radius, spoke_width, spokes, mount_radius, mount_hole, unit_factor, unit_label)[source]
+

given a set of constraints +- generate the svg path for the gear spokes +- lies between mount_radius (inner hole) and root_radius (bottom of the teeth) +- spoke width also defines the spacing at the root_radius +- mount_radius is adjusted so that spokes fit if there is room +- if no room (collision) then spokes not drawn

+
+ +
+
+sizes(**kw)[source]
+
+ +
+ +
+
+class boxes.gears.OptionParser(prog=None, usage=None, description=None, epilog=None, parents=[], formatter_class=<class 'argparse.HelpFormatter'>, prefix_chars='-', fromfile_prefix_chars=None, argument_default=None, conflict_handler='error', add_help=True, allow_abbrev=True)[source]
+

Bases: ArgumentParser

+
+
+add_option(short, long_, **kw)[source]
+
+ +
+
+types = {'float': <class 'float'>, 'inkbool': <function inkbool>, 'int': <class 'int'>, 'string': <class 'str'>}
+
+ +
+ +
+
+boxes.gears.gear_calculations(num_teeth, circular_pitch, pressure_angle, clearance=0, ring_gear=False, profile_shift=0.0)[source]
+

Put base calcs for spur/ring gears in one place. +- negative profile shifting helps against undercut.

+
+ +
+
+boxes.gears.generate_rack_points(tooth_count, pitch, addendum, pressure_angle, base_height, tab_length, clearance=0, draw_guides=False)[source]
+

Return path (suitable for svg) of the Rack gear. +- rack gear uses straight sides

+
+
    +
  • involute on a circle of infinite radius is a simple linear ramp

  • +
+
+
    +
  • the meshing circle touches at y = 0,

  • +
  • the highest elevation of the teeth is at y = +addendum

  • +
  • the lowest elevation of the teeth is at y = -addendum-clearance

  • +
  • the base_height extends downwards from the lowest elevation.

  • +
  • we generate this middle tooth exactly centered on the y=0 line. +(one extra tooth on the right hand side, if number of teeth is even)

  • +
+
+ +
+
+boxes.gears.generate_spur_points(teeth, base_radius, pitch_radius, outer_radius, root_radius, accuracy_involute, accuracy_circular)[source]
+

given a set of core gear params +- generate the svg path for the gear

+
+ +
+
+boxes.gears.have_undercut(teeth, pitch_angle=20.0, k=1.0)[source]
+

returns true if the specified number of teeth would +cause an undercut.

+
+ +
+
+boxes.gears.inkbool(val)[source]
+
+ +
+
+boxes.gears.involute_intersect_angle(Rb, R)[source]
+
+ +
+
+boxes.gears.linspace(a, b, n)[source]
+

return list of linear interp of a to b in n steps +- if a and b are ints - you’ll get an int result. +- n must be an integer

+
+ +
+
+boxes.gears.point_on_circle(radius, angle)[source]
+

return xy coord of the point at distance radius from origin at angle

+
+ +
+
+boxes.gears.undercut_max_k(teeth, pitch_angle=20.0)[source]
+

computes the maximum k value for a given teeth count and pitch_angle +so that no undercut occurs.

+
+ +
+
+boxes.gears.undercut_min_angle(teeth, k=1.0)[source]
+

computes the minimum pitch angle, to that the given teeth count (and +profile shift) cause no undercut.

+
+ +
+
+boxes.gears.undercut_min_teeth(pitch_angle, k=1.0)[source]
+

computes the minimum tooth count for a +spur gear so that no undercut with the given pitch_angle (in deg) +and an addendum = k * metric_module, where 0 < k < 1

+

Note: +The return value should be rounded upwards for perfect safety. E.g. +min_teeth = int(math.ceil(undercut_min_teeth(20.0))) # 18, not 17

+
+ +
+
+

boxes.lids module

+
+
+

boxes.mounts module

+
+
+

boxes.parts module

+
+
+class boxes.parts.Parts(boxes)[source]
+

Bases: object

+
+
+concaveKnob(diameter, n=3, rounded=0.2, angle=70, hole=0, callback=None, move='')[source]
+

Knob with dents to be easier to be gripped

+
+
Parameters:
+
    +
  • diameter – diameter of the knob

  • +
  • n – (Default value = 3) number of dents

  • +
  • rounded – (Default value = 0.2) proportion of circumferen remaining

  • +
  • angle – (Default value = 70) angle the dents meet the circumference

  • +
  • hole – (Default value = 0)

  • +
  • callback – (Default value = None) called in the center

  • +
  • move – (Defaultvalue = None)

  • +
+
+
+
+ +
+
+disc(diameter, hole=0, callback=None, move='', label='')[source]
+

Simple disc

+
+
Parameters:
+
    +
  • diameter – diameter of the disc

  • +
  • hole – (Default value = 0)

  • +
  • callback – (Default value = None) called in the center

  • +
  • move – (Defaultvalue = None)

  • +
+
+
+
+ +
+
+ringSegment(r_outside, r_inside, angle, n=1, move=None)[source]
+

Ring Segment

+
+
Parameters:
+
    +
  • r_outside – outer radius

  • +
  • r_inside – inner radius

  • +
  • angle – anlge the segment is spanning

  • +
  • n – (Default value = 1) number of segments

  • +
  • move – (Defaultvalue = None)

  • +
+
+
+
+ +
+
+waivyKnob(diameter, n=20, angle=45, hole=0, callback=None, move='')[source]
+

Disc with a waivy edge to be easier to be gripped

+
+
Parameters:
+
    +
  • diameter – diameter of the knob

  • +
  • n – (Default value = 20) number of waves

  • +
  • angle – (Default value = 45) maximum angle of the wave

  • +
  • hole – (Default value = 0)

  • +
  • callback – (Default value = None) called in the center

  • +
  • move – (Defaultvalue = None)

  • +
+
+
+
+ +
+ +
+
+boxes.parts.arcOnCircle(spanning_angle, outgoing_angle, r=1.0)[source]
+
+ +
+
+

boxes.pulley module

+

// Parametric Pulley with multiple belt profiles +// by droftarts January 2012

+

// Based on pulleys by: +// http://www.thingiverse.com/thing:11256 by me! +// https://github.com/prusajr/PrusaMendel by Josef Prusa +// http://www.thingiverse.com/thing:3104 by GilesBathgate +// http://www.thingiverse.com/thing:2079 by nophead

+

// dxf tooth data from http://oem.cadregister.com/asp/PPOW_Entry.asp?company=915217&elementID=07807803/METRIC/URETH/WV0025/F +// pulley diameter checked and modelled from data at http://www.sdp-si.com/D265/HTML/D265T016.html

+
+
+class boxes.pulley.Pulley(boxes)[source]
+

Bases: object

+
+
+diameter(teeth, profile)[source]
+
+ +
+
+drawPoints(lines, kerfdir=1)[source]
+
+ +
+
+classmethod getProfiles()[source]
+
+ +
+
+profile_data = {'40DP': (0.457, 1.226), 'AT5': (1.19, 4.268), 'GT2_2mm': (0.764, 1.494), 'GT2_3mm': (1.169, 2.31), 'GT2_5mm': (1.969, 3.952), 'H': (1.905, 5.359), 'HTD_3mm': (1.289, 2.27), 'HTD_5mm': (2.199, 3.781), 'HTD_8mm': (3.607, 6.603), 'MXL': (0.508, 1.321), 'T10': (2.5, 6.13), 'T2_5': (0.7, 1.678), 'T5': (1.19, 3.264), 'XL': (1.27, 3.051)}
+
+ +
+
+spacing = {'40DP': (False, 2.07264, 0.1778), 'AT5': (True, 0.6523, 1.591, 1.064), 'GT2_2mm': (False, 2, 0.254), 'GT2_3mm': (False, 3, 0.381), 'GT2_5mm': (False, 5, 0.5715), 'H': (False, 9.525, 0.381), 'HTD_3mm': (False, 3, 0.381), 'HTD_5mm': (False, 5, 0.5715), 'HTD_8mm': (False, 8, 0.6858), 'MXL': (False, 2.032, 0.254), 'T10': (False, 10, 0.93), 'T2_5': (True, 0.7467, 0.796, 1.026), 'T5': (True, 0.6523, 1.591, 1.064), 'XL': (False, 5.08, 0.254)}
+
+ +
+
+teeth = {'40DP': [[-0.612775, -0.5], [-0.612775, 0], [-0.574719, 0.010187], [-0.546453, 0.0381], [-0.355953, 0.3683], [-0.327604, 0.405408], [-0.291086, 0.433388], [-0.248548, 0.451049], [-0.202142, 0.4572], [0.202494, 0.4572], [0.248653, 0.451049], [0.291042, 0.433388], [0.327609, 0.405408], [0.356306, 0.3683], [0.546806, 0.0381], [0.574499, 0.010187], [0.612775, 0], [0.612775, -0.5]], 'AT5': [[-2.134129, -0.75], [-2.134129, 0], [-2.058023, 0.005488], [-1.984595, 0.021547], [-1.914806, 0.047569], [-1.849614, 0.082947], [-1.789978, 0.127073], [-1.736857, 0.179338], [-1.691211, 0.239136], [-1.653999, 0.305859], [-1.349199, 0.959203], [-1.286933, 1.054635], [-1.201914, 1.127346], [-1.099961, 1.173664], [-0.986896, 1.18992], [0.986543, 1.18992], [1.099614, 1.173664], [1.201605, 1.127346], [1.286729, 1.054635], [1.349199, 0.959203], [1.653646, 0.305859], [1.690859, 0.239136], [1.73651, 0.179338], [1.789644, 0.127073], [1.849305, 0.082947], [1.914539, 0.047569], [1.984392, 0.021547], [2.057906, 0.005488], [2.134129, 0], [2.134129, -0.75]], 'GT2_2mm': [[-0.747183, -0.5], [-0.747183, 0], [-0.647876, 0.037218], [-0.598311, 0.130528], [-0.578556, 0.238423], [-0.547158, 0.343077], [-0.504649, 0.443762], [-0.451556, 0.53975], [-0.358229, 0.636924], [-0.2484, 0.707276], [-0.127259, 0.750044], [0, 0.76447], [0.127259, 0.750044], [0.2484, 0.707276], [0.358229, 0.636924], [0.451556, 0.53975], [0.504797, 0.443762], [0.547291, 0.343077], [0.578605, 0.238423], [0.598311, 0.130528], [0.648009, 0.037218], [0.747183, 0], [0.747183, -0.5]], 'GT2_3mm': [[-1.155171, -0.5], [-1.155171, 0], [-1.065317, 0.016448], [-0.989057, 0.062001], [-0.93297, 0.130969], [-0.90364, 0.217664], [-0.863705, 0.408181], [-0.800056, 0.591388], [-0.713587, 0.765004], [-0.60519, 0.926747], [-0.469751, 1.032548], [-0.320719, 1.108119], [-0.162625, 1.153462], [0, 1.168577], [0.162625, 1.153462], [0.320719, 1.108119], [0.469751, 1.032548], [0.60519, 0.926747], [0.713587, 0.765004], [0.800056, 0.591388], [0.863705, 0.408181], [0.90364, 0.217664], [0.932921, 0.130969], [0.988924, 0.062001], [1.065168, 0.016448], [1.155171, 0], [1.155171, -0.5]], 'GT2_5mm': [[-1.975908, -0.75], [-1.975908, 0], [-1.797959, 0.03212], [-1.646634, 0.121224], [-1.534534, 0.256431], [-1.474258, 0.426861], [-1.446911, 0.570808], [-1.411774, 0.712722], [-1.368964, 0.852287], [-1.318597, 0.989189], [-1.260788, 1.123115], [-1.195654, 1.25375], [-1.12331, 1.380781], [-1.043869, 1.503892], [-0.935264, 1.612278], [-0.817959, 1.706414], [-0.693181, 1.786237], [-0.562151, 1.851687], [-0.426095, 1.9027], [-0.286235, 1.939214], [-0.143795, 1.961168], [0, 1.9685], [0.143796, 1.961168], [0.286235, 1.939214], [0.426095, 1.9027], [0.562151, 1.851687], [0.693181, 1.786237], [0.817959, 1.706414], [0.935263, 1.612278], [1.043869, 1.503892], [1.123207, 1.380781], [1.195509, 1.25375], [1.26065, 1.123115], [1.318507, 0.989189], [1.368956, 0.852287], [1.411872, 0.712722], [1.447132, 0.570808], [1.474611, 0.426861], [1.534583, 0.256431], [1.646678, 0.121223], [1.798064, 0.03212], [1.975908, 0], [1.975908, -0.75]], 'H': [[-2.6797, -1], [-2.6797, 0], [-2.600907, 0.006138], [-2.525342, 0.024024], [-2.45412, 0.052881], [-2.388351, 0.091909], [-2.329145, 0.140328], [-2.277614, 0.197358], [-2.234875, 0.262205], [-2.202032, 0.334091], [-1.75224, 1.57093], [-1.719538, 1.642815], [-1.676883, 1.707663], [-1.62542, 1.764693], [-1.566256, 1.813112], [-1.500512, 1.85214], [-1.4293, 1.880997], [-1.353742, 1.898883], [-1.274949, 1.905021], [1.275281, 1.905021], [1.354056, 1.898883], [1.429576, 1.880997], [1.500731, 1.85214], [1.566411, 1.813112], [1.625508, 1.764693], [1.676919, 1.707663], [1.719531, 1.642815], [1.752233, 1.57093], [2.20273, 0.334091], [2.235433, 0.262205], [2.278045, 0.197358], [2.329455, 0.140328], [2.388553, 0.091909], [2.454233, 0.052881], [2.525384, 0.024024], [2.600904, 0.006138], [2.6797, 0], [2.6797, -1]], 'HTD_3mm': [[-1.135062, -0.5], [-1.135062, 0], [-1.048323, 0.015484], [-0.974284, 0.058517], [-0.919162, 0.123974], [-0.889176, 0.206728], [-0.81721, 0.579614], [-0.800806, 0.653232], [-0.778384, 0.72416], [-0.750244, 0.792137], [-0.716685, 0.856903], [-0.678005, 0.918199], [-0.634505, 0.975764], [-0.586483, 1.029338], [-0.534238, 1.078662], [-0.47807, 1.123476], [-0.418278, 1.16352], [-0.355162, 1.198533], [-0.289019, 1.228257], [-0.22015, 1.25243], [-0.148854, 1.270793], [-0.07543, 1.283087], [-0.000176, 1.28905], [0.075081, 1.283145], [0.148515, 1.270895], [0.219827, 1.252561], [0.288716, 1.228406], [0.354879, 1.19869], [0.418018, 1.163675], [0.477831, 1.123623], [0.534017, 1.078795], [0.586276, 1.029452], [0.634307, 0.975857], [0.677809, 0.91827], [0.716481, 0.856953], [0.750022, 0.792167], [0.778133, 0.724174], [0.800511, 0.653236], [0.816857, 0.579614], [0.888471, 0.206728], [0.919014, 0.123974], [0.974328, 0.058517], [1.048362, 0.015484], [1.135062, 0], [1.135062, -0.5]], 'HTD_5mm': [[-1.89036, -0.75], [-1.89036, 0], [-1.741168, 0.02669], [-1.61387, 0.100806], [-1.518984, 0.21342], [-1.467026, 0.3556], [-1.427162, 0.960967], [-1.398568, 1.089602], [-1.359437, 1.213531], [-1.310296, 1.332296], [-1.251672, 1.445441], [-1.184092, 1.552509], [-1.108081, 1.653042], [-1.024167, 1.746585], [-0.932877, 1.832681], [-0.834736, 1.910872], [-0.730271, 1.980701], [-0.62001, 2.041713], [-0.504478, 2.09345], [-0.384202, 2.135455], [-0.259708, 2.167271], [-0.131524, 2.188443], [-0.000176, 2.198511], [0.131296, 2.188504], [0.259588, 2.167387], [0.384174, 2.135616], [0.504527, 2.093648], [0.620123, 2.04194], [0.730433, 1.980949], [0.834934, 1.911132], [0.933097, 1.832945], [1.024398, 1.746846], [1.108311, 1.653291], [1.184308, 1.552736], [1.251865, 1.445639], [1.310455, 1.332457], [1.359552, 1.213647], [1.39863, 1.089664], [1.427162, 0.960967], [1.467026, 0.3556], [1.518984, 0.21342], [1.61387, 0.100806], [1.741168, 0.02669], [1.89036, 0], [1.89036, -0.75]], 'HTD_8mm': [[-3.301471, -1], [-3.301471, 0], [-3.16611, 0.012093], [-3.038062, 0.047068], [-2.919646, 0.10297], [-2.813182, 0.177844], [-2.720989, 0.269734], [-2.645387, 0.376684], [-2.588694, 0.496739], [-2.553229, 0.627944], [-2.460801, 1.470025], [-2.411413, 1.691917], [-2.343887, 1.905691], [-2.259126, 2.110563], [-2.158035, 2.30575], [-2.041518, 2.490467], [-1.910478, 2.66393], [-1.76582, 2.825356], [-1.608446, 2.973961], [-1.439261, 3.10896], [-1.259169, 3.22957], [-1.069074, 3.335006], [-0.869878, 3.424485], [-0.662487, 3.497224], [-0.447804, 3.552437], [-0.226732, 3.589341], [-0.000176, 3.607153], [0.226511, 3.589461], [0.447712, 3.552654], [0.66252, 3.497516], [0.870027, 3.424833], [1.069329, 3.33539], [1.259517, 3.229973], [1.439687, 3.109367], [1.608931, 2.974358], [1.766344, 2.825731], [1.911018, 2.664271], [2.042047, 2.490765], [2.158526, 2.305998], [2.259547, 2.110755], [2.344204, 1.905821], [2.411591, 1.691983], [2.460801, 1.470025], [2.553229, 0.627944], [2.588592, 0.496739], [2.645238, 0.376684], [2.720834, 0.269734], [2.81305, 0.177844], [2.919553, 0.10297], [3.038012, 0.047068], [3.166095, 0.012093], [3.301471, 0], [3.301471, -1]], 'MXL': [[-0.660421, -0.5], [-0.660421, 0], [-0.621898, 0.006033], [-0.587714, 0.023037], [-0.560056, 0.049424], [-0.541182, 0.083609], [-0.417357, 0.424392], [-0.398413, 0.458752], [-0.370649, 0.48514], [-0.336324, 0.502074], [-0.297744, 0.508035], [0.297744, 0.508035], [0.336268, 0.502074], [0.370452, 0.48514], [0.39811, 0.458752], [0.416983, 0.424392], [0.540808, 0.083609], [0.559752, 0.049424], [0.587516, 0.023037], [0.621841, 0.006033], [0.660421, 0], [0.660421, -0.5]], 'T10': [[-3.06511, -1], [-3.06511, 0], [-2.971998, 0.007239], [-2.882718, 0.028344], [-2.79859, 0.062396], [-2.720931, 0.108479], [-2.651061, 0.165675], [-2.590298, 0.233065], [-2.539962, 0.309732], [-2.501371, 0.394759], [-1.879071, 2.105025], [-1.840363, 2.190052], [-1.789939, 2.266719], [-1.729114, 2.334109], [-1.659202, 2.391304], [-1.581518, 2.437387], [-1.497376, 2.47144], [-1.408092, 2.492545], [-1.314979, 2.499784], [1.314979, 2.499784], [1.408091, 2.492545], [1.497371, 2.47144], [1.581499, 2.437387], [1.659158, 2.391304], [1.729028, 2.334109], [1.789791, 2.266719], [1.840127, 2.190052], [1.878718, 2.105025], [2.501018, 0.394759], [2.539726, 0.309732], [2.59015, 0.233065], [2.650975, 0.165675], [2.720887, 0.108479], [2.798571, 0.062396], [2.882713, 0.028344], [2.971997, 0.007239], [3.06511, 0], [3.06511, -1]], 'T2_5': [[-0.839258, -0.5], [-0.839258, 0], [-0.770246, 0.021652], [-0.726369, 0.079022], [-0.529167, 0.620889], [-0.485025, 0.67826], [-0.416278, 0.699911], [0.416278, 0.699911], [0.484849, 0.67826], [0.528814, 0.620889], [0.726369, 0.079022], [0.770114, 0.021652], [0.839258, 0], [0.839258, -0.5]], 'T5': [[-1.632126, -0.5], [-1.632126, 0], [-1.568549, 0.004939], [-1.507539, 0.019367], [-1.450023, 0.042686], [-1.396912, 0.074224], [-1.349125, 0.113379], [-1.307581, 0.159508], [-1.273186, 0.211991], [-1.246868, 0.270192], [-1.009802, 0.920362], [-0.983414, 0.978433], [-0.949018, 1.030788], [-0.907524, 1.076798], [-0.859829, 1.115847], [-0.80682, 1.147314], [-0.749402, 1.170562], [-0.688471, 1.184956], [-0.624921, 1.189895], [0.624971, 1.189895], [0.688622, 1.184956], [0.749607, 1.170562], [0.807043, 1.147314], [0.860055, 1.115847], [0.907754, 1.076798], [0.949269, 1.030788], [0.9837, 0.978433], [1.010193, 0.920362], [1.246907, 0.270192], [1.273295, 0.211991], [1.307726, 0.159508], [1.349276, 0.113379], [1.397039, 0.074224], [1.450111, 0.042686], [1.507589, 0.019367], [1.568563, 0.004939], [1.632126, 0], [1.632126, -0.5]], 'XL': [[-1.525411, -1], [-1.525411, 0], [-1.41777, 0.015495], [-1.320712, 0.059664], [-1.239661, 0.129034], [-1.180042, 0.220133], [-0.793044, 1.050219], [-0.733574, 1.141021], [-0.652507, 1.210425], [-0.555366, 1.254759], [-0.447675, 1.270353], [0.447675, 1.270353], [0.555366, 1.254759], [0.652507, 1.210425], [0.733574, 1.141021], [0.793044, 1.050219], [1.180042, 0.220133], [1.239711, 0.129034], [1.320844, 0.059664], [1.417919, 0.015495], [1.525411, 0], [1.525411, -1]]}
+
+ +
+ +
+
+boxes.pulley.mirrorx(points)[source]
+
+ +
+
+boxes.pulley.tooth_spaceing_curvefit(teeth, b, c, d)[source]
+
+ +
+
+boxes.pulley.tooth_spacing(teeth, tooth_pitch, pitch_line_offset)[source]
+
+ +
+
+

boxes.robot module

+
+
+class boxes.robot.RobotArg(includenone=False)[source]
+

Bases: object

+
+
+choices()[source]
+
+ +
+
+html(name, default, translate)[source]
+
+ +
+ +
+
+class boxes.robot.RobotArmMM(boxes, servo, servo2=None)[source]
+

Bases: _RobotArm

+

Robot arm segment with two parallel servos

+
+ +
+
+class boxes.robot.RobotArmMm(boxes, servo, servo2=None)[source]
+

Bases: _RobotArm

+

Robot arm segment with two orthogonal servos

+
+ +
+
+class boxes.robot.RobotArmMu(boxes, servo, servo2=None)[source]
+

Bases: _RobotArm

+

Robot arm segment with a servo and an orthogonal sets of hinge knuckles

+
+ +
+
+class boxes.robot.RobotArmUU(boxes, servo, servo2=None)[source]
+

Bases: _RobotArm

+

Robot arm segment with two parallel sets of hinge knuckles

+
+ +
+
+class boxes.robot.RobotArmUu(boxes, servo, servo2=None)[source]
+

Bases: _RobotArm

+

Robot arm segment with two orthogonal sets of hinge knuckles

+
+ +
+
+

boxes.servos module

+
+
+class boxes.servos.EyeEdge(boxes, servo, fingerHoles=None, driven=False, outset=False, **kw)[source]
+

Bases: FingerHoleEdge

+
+
+char = 'm'
+
+ +
+
+margin()[source]
+

Space needed right of the starting point

+
+ +
+
+startwidth()[source]
+
+ +
+ +
+
+class boxes.servos.Servo(boxes, axle=3)[source]
+

Bases: object

+
+
+edges(edges)[source]
+
+ +
+ +
+
+class boxes.servos.Servo9g(boxes, axle=3)[source]
+

Bases: Servo

+
+
+axle_pos = 6.0
+
+ +
+
+bottom(x=0.0, y=0.0, angle=90.0)[source]
+
+ +
+
+front(x=0.0, y=0.0, angle=90.0)[source]
+
+ +
+
+height = 22.5
+
+ +
+
+hinge_depth()[source]
+
+ +
+
+hinge_width()[source]
+
+ +
+
+length = 28.0
+
+ +
+
+servo_axle = 4.6
+
+ +
+
+top(x=0.0, y=0.0, angle=90.0)[source]
+
+ +
+
+width = 12.0
+
+ +
+ +
+
+class boxes.servos.Servo9gt(boxes, axle=3)[source]
+

Bases: Servo9g

+
+
+bottom(x=0.0, y=0.0, angle=90.0)[source]
+
+ +
+
+front(x=0.0, y=0.0, angle=90.0)[source]
+
+ +
+
+height = 35
+
+ +
+
+top(x=0.0, y=0.0, angle=90.0)[source]
+
+ +
+ +
+
+class boxes.servos.ServoArg(includenone=False)[source]
+

Bases: object

+
+
+choices()[source]
+
+ +
+
+html(name, default, translate)[source]
+
+ +
+ +
+
+boxes.servos.buildEdges(boxes, servo, chars='mMnN')[source]
+
+ +
+
+

boxes.svgutil module

+
+
+boxes.svgutil.getSizeInMM(tree)[source]
+
+ +
+
+boxes.svgutil.getViewBox(tree)[source]
+
+ +
+
+boxes.svgutil.svgMerge(box, inkscape, output)[source]
+
+ +
+
+boxes.svgutil.ticksPerMM(tree)[source]
+
+ +
+
+

boxes.vectors module

+
+
+boxes.vectors.circlepoint(r, a)[source]
+
+ +
+
+boxes.vectors.dotproduct(v1, v2)[source]
+

Dot product

+
+ +
+
+boxes.vectors.kerf(points, k, closed=True)[source]
+

Outset points by k +Assumes a closed loop of points

+
+ +
+
+boxes.vectors.mmul(m0, m1)[source]
+
+ +
+
+boxes.vectors.normalize(v)[source]
+

set lenght of vector to one

+
+ +
+
+boxes.vectors.rotm(angle)[source]
+

Rotation matrix

+
+ +
+
+boxes.vectors.tangent(x, y, r)[source]
+

angle and length of a tangent to a circle at x,y with raduis r

+
+ +
+
+boxes.vectors.vadd(v1, v2)[source]
+

Sum of two vectors

+
+ +
+
+boxes.vectors.vclip(v, length)[source]
+
+ +
+
+boxes.vectors.vdiff(p1, p2)[source]
+

vector from point1 to point2

+
+ +
+
+boxes.vectors.vlength(v)[source]
+
+ +
+
+boxes.vectors.vorthogonal(v)[source]
+

orthogonal vector

+
+ +
+
+boxes.vectors.vscalmul(v, a)[source]
+

scale vector by a

+
+ +
+
+boxes.vectors.vtransl(v, m)[source]
+
+ +
+
+

Module contents

+
+
+class boxes.ArgparseEdgeType(edges=None)[source]
+

Bases: object

+

argparse type to select from a set of edge types

+
+
+edges = []
+
+ +
+
+html(name, default, translate)[source]
+
+ +
+
+inx(name, viewname, arg)[source]
+
+ +
+
+names = {'C': 'Click on (top)', 'D': 'Dove Tail Joint (opposing side)', 'E': 'Straight Edge (outset by thickness)', 'F': 'Finger Joint (opposing side)', 'G': 'Edge with pear shaped mounting holes', 'I': 'Edge with hinge pin', 'J': 'Edge with hinge pin (other end)', 'K': 'Edge with hinge pin (both ends)', 'L': 'Edge for slide on lid (box back)', 'M': 'Edge for slide on lid (box left)', 'N': 'Edge for slide on lid (box right)', 'O': 'Edge with chest hinge (other end)', 'P': 'Edge with chest hinge (other end)', 'Q': 'Edge opposing a chest hinge', 'R': 'Rack (and pinion) Edge', 'S': 'Stackable (top)', 'T': 'Triangle for handle', 'U': 'Edge with cabinet hinges top side', 'V': 'Edge with cabinet hinges 90° lid', 'X': 'Flex cut', 'Y': 'Handle with holes for parallel finger joint', 'Z': 'Edge with grooves (opposing side)', 'c': 'Click on (bottom side)', 'd': 'Dove Tail Joint', 'e': 'Straight Edge', 'f': 'Finger Joint', 'g': 'Corrugated edge useful as an gipping area', 'h': 'Edge (parallel Finger Joint Holes)', 'i': 'Straight edge with hinge eye', 'j': 'Straight edge with hinge eye (other end)', 'k': 'Straight edge with hinge eye (both ends)', 'l': 'Edge for slide on lid (back)', 'm': 'Edge for slide on lid (left)', 'n': 'Edge for slide on lid (right)', 'o': 'Edge with chest hinge', 'p': 'Edge with chest hinge', 'q': 'Edge with pins for an chest hinge', 's': 'Stackable (bottom, finger joint holes)', 't': 'Triangle for handle', 'u': 'Edge with cabinet hinges', 'v': 'Edge with cabinet hinges for 90° lid', 'y': 'Handle for e.g. a drawer', 'z': 'Edge with grooves', '|': 'Edge (orthogonal Finger Joint Holes)', 'Š': 'Stackable edge with finger holes (top)', 'š': 'Stackable feet (bottom)'}
+
+ +
+ +
+
+class boxes.BoolArg[source]
+

Bases: object

+
+
+html(name, default, _)[source]
+
+ +
+ +
+
+class boxes.Boxes[source]
+

Bases: object

+

Main class – Generator should sub class this

+
+
+NEMA(size, x=0, y=0, angle=0, screwholes=None)[source]
+

Draw holes for mounting a NEMA stepper motor

+
+
Parameters:
+
    +
  • size – Nominal size in tenths of inches

  • +
  • x – (Default value = 0)

  • +
  • y – (Default value = 0)

  • +
  • angle – (Default value = 0)

  • +
+
+
+
+ +
+
+TX(size, x=0, y=0, angle=0)[source]
+

Draw a star pattern

+
+
Parameters:
+
    +
  • size – 1 to 100

  • +
  • x – (Default value = 0)

  • +
  • y – (Default value = 0)

  • +
  • angle – (Default value = 0)

  • +
+
+
+
+ +
+
+addPart(part, name=None)[source]
+

Add Edge or other part instance to this one and add it as attribute

+
+
Parameters:
+
    +
  • part – Callable

  • +
  • name – (Default value = None) attribute name (__name__ as default)

  • +
+
+
+
+ +
+
+addParts(parts)[source]
+
+ +
+
+addSettingsArgs(settings, prefix=None, **defaults)[source]
+
+ +
+
+adjustSize(l, e1=True, e2=True)[source]
+
+ +
+
+bedBoltHole(length, bedBoltSettings=None, tabs=0)[source]
+

Draw an edge with slot for a bed bolt

+
+
Parameters:
+
    +
  • length – length of the edge in mm

  • +
  • bedBoltSettings – (Default value = None) Dimmensions of the slot

  • +
+
+
+
+ +
+
+buildArgParser(*l, **kw)[source]
+

Add commonly used arguments

+
+
Parameters:
+
    +
  • *l – parameter names

  • +
  • **kw – parameters with new default values

  • +
+
+
+

Supported parameters are

+
    +
  • floats: x, y, h, hi

  • +
  • argparseSections: sx, sy, sh

  • +
  • ArgparseEdgeType: bottom_edge, top_edge

  • +
  • boolarg: outside

  • +
  • str (selection): nema_mount

  • +
+
+ +
+
+cc(callback, number, x=0.0, y=None)[source]
+

Call callback from edge of a part

+
+
Parameters:
+
    +
  • callback – callback (callable or list of callables)

  • +
  • number – number of the callback

  • +
  • x – (Default value = 0.0) x position to be call on

  • +
  • y – (Default value = None) y position to be called on (default does burn correction)

  • +
+
+
+
+ +
+
+circle(x, y, r)[source]
+

Draw a round disc

+
+
Parameters:
+
    +
  • x – position

  • +
  • y – postion

  • +
  • r – radius

  • +
+
+
+
+ +
+
+close()[source]
+

Finish rendering

+

Flush canvas to disk and convert output to requested format if needed. +Call after .render()

+
+ +
+
+corner(degrees, radius=0, tabs=0)[source]
+

Draw a corner

+

This is what does the burn corrections

+
+
Parameters:
+
    +
  • degrees – angle

  • +
  • radius – (Default value = 0)

  • +
+
+
+
+ +
+
+curveTo(x1, y1, x2, y2, x3, y3)[source]
+

control point 1, control point 2, end point

+
+
Parameters:
+
    +
  • x1

  • +
  • y1

  • +
  • x2

  • +
  • y2

  • +
  • x3

  • +
  • y3

  • +
+
+
+
+ +
+
+dHole(x, y, r=None, d=None, w=None, rel_w=0.75, angle=0)[source]
+

Draw a hole for a shaft with flat edge - D shaped hole

+
+
Parameters:
+
    +
  • x – center position

  • +
  • y – center position

  • +
  • r – radius (overrides d)

  • +
  • d – diameter

  • +
  • w – width measured against flat side in mm

  • +
  • rel_w – width in percent

  • +
  • angle – orentation (rotation) of the flat side

  • +
+
+
+
+ +
+
+description = ''
+
+ +
+
+edge(length, tabs=0)[source]
+

Simple line +:param length: length in mm

+
+ +
+
+edgeCorner(edge1, edge2, angle=90)[source]
+

Make a corner between two Edges. Take width of edges into account

+
+ +
+
+fillHoles(pattern, border, max_radius, hspace=3, bspace=0, min_radius=0.5, style='round', bar_length=50, max_random=1000)[source]
+

fill a polygon defined by its outline with holes

+
+
Parameters:
+
    +
  • pattern – defines the hole pattern - currently “random”, “hex”, “square” “hbar” or “vbar” are supported

  • +
  • border – array with coordinate [(x0,y0), (x1,y1),…] of the border polygon

  • +
  • max_radius – maximum hole radius

  • +
  • hspace – space between holes

  • +
  • bspace – space to border

  • +
  • min_radius – minimum hole radius

  • +
  • style – defines hole style - currently one of “round”, “triangle”, “square”, “hexagon” or “octagon”

  • +
  • bar_length – maximum bar length

  • +
  • max_random – maximum number of random holes

  • +
+
+
+
+ +
+
+fingerHoleRectangle(dx, dy, x=0.0, y=0.0, angle=0.0, outside=False)[source]
+

Place finger holes for four walls - attaching a box on this plane

+
+
Parameters:
+
    +
  • dx – size in x direction

  • +
  • dy – size in y direction

  • +
  • x – x position of the center

  • +
  • y – y position of the center

  • +
  • angle – angle in which the rectangle is placed

  • +
  • outside – meassure size from the outside of the walls - not the inside

  • +
+
+
+
+ +
+
+flangedWall(x, y, edges='FFFF', flanges=None, r=0.0, callback=None, move=None, label='')[source]
+

Rectangular wall with flanges extending the regular size

+

This is similar to the rectangularWall but it may extend to either +side. Sides with flanges may only have e, E, or F edges - the later +being replaced with fingerHoles.

+
+
Parameters:
+
    +
  • x – width

  • +
  • y – height

  • +
  • edges – (Default value = “FFFF”) bottom, right, top, left

  • +
  • flanges – (Default value = None) list of width of the flanges

  • +
  • r – radius of the corners of the flange

  • +
  • callback – (Default value = None)

  • +
  • move – (Default value = None)

  • +
  • label – rendered to identify parts, it is not ment to be cut or etched (Default value = “”)

  • +
+
+
+
+ +
+
+flatHole(x, y, r=None, d=None, w=None, rel_w=0.75, angle=0)[source]
+

Draw a hole for a shaft with two opposed flat edges - ( ) shaped hole

+
+
Parameters:
+
    +
  • x – center position

  • +
  • y – center position

  • +
  • r – radius (overrides d)

  • +
  • d – diameter

  • +
  • w – width measured against flat side in mm

  • +
  • rel_w – width in percent

  • +
  • angle – orientation (rotation) of the flat sides

  • +
+
+
+
+ +
+
+flex2D(x, y, width=1)[source]
+

Fill a rectangle with a pattern allowing bending in both axis

+
+
Parameters:
+
    +
  • x – width

  • +
  • y – height

  • +
  • width – width between the lines of the pattern in multiples of thickness

  • +
+
+
+
+ +
+
+getEntry(param, idx)[source]
+

Get entry from list or items itself

+
+
Parameters:
+
    +
  • param – list or item

  • +
  • idx – index in list

  • +
+
+
+
+ +
+
+grip(length, depth)[source]
+

Corrugated edge useful as an gipping area

+
+
Parameters:
+
    +
  • length – length

  • +
  • depth – depth of the grooves

  • +
+
+
+
+ +
+
+handle(x, h, hl, r=30)[source]
+

Creates an Edge with a handle

+
+
Parameters:
+
    +
  • x – width in mm

  • +
  • h – height in mm

  • +
  • hl – height if th grip hole

  • +
  • r – (Default value = 30) radius of the corners

  • +
+
+
+
+ +
+
+hexHolesCircle(d, settings=None)[source]
+

Fill circle with holes in a hex pattern

+
+
Parameters:
+
    +
  • d – diameter of the circle

  • +
  • settings – (Default value = None)

  • +
+
+
+
+ +
+
+hexHolesHex(h, settings=None, grow=None)[source]
+

Fill a hexagon with holes in a hex pattern

+
+
Parameters:
+
    +
  • h – height

  • +
  • settings – (Default value = None)

  • +
  • grow – (Default value = None)

  • +
+
+
+
+ +
+
+hexHolesPlate(x, y, rc, settings=None)[source]
+

Fill a plate with holes in a hex pattern

+
+
Parameters:
+
    +
  • x – width

  • +
  • y – height

  • +
  • rc – radius of the corners

  • +
  • settings – (Default value = None)

  • +
+
+
+
+ +
+
+hexHolesRectangle(x, y, settings=None, skip=None)[source]
+

Fills a rectangle with holes in a hex pattern.

+

Settings have: +r : radius of holes +b : space between holes +style : what types of holes (not yet implemented)

+
+
Parameters:
+
    +
  • x – width

  • +
  • y – height

  • +
  • settings – (Default value = None)

  • +
  • skip – (Default value = None) function to check if hole should be present +gets x, y, r, b, posx, posy

  • +
+
+
+
+ +
+
+hole(x, y, r=0.0, d=0.0, tabs=0)[source]
+

Draw a round hole

+
+
Parameters:
+
    +
  • x – position

  • +
  • y – postion

  • +
  • r – radius

  • +
+
+
+
+ +
+
+latch(length, positive=True, reverse=False)[source]
+

Latch to fix a flex box door to the box

+
+
Parameters:
+
    +
  • length – length in mm

  • +
  • positive – (Default value = True) False: Door side; True: Box side

  • +
  • reverse – (Default value = False) True when running away from the latch

  • +
+
+
+
+ +
+
+mirrorX(f, offset=0.0)[source]
+

Wrap a function to draw mirrored at the y axis

+
+
Parameters:
+
    +
  • f – function to wrap

  • +
  • offset – (default value = 0.0) axis to mirror at

  • +
+
+
+
+ +
+
+mirrorY(f, offset=0.0)[source]
+

Wrap a function to draw mirrored at the x axis

+
+
Parameters:
+
    +
  • f – function to wrap

  • +
  • offset – (default value = 0.0) axis to mirror at

  • +
+
+
+
+ +
+
+mountingHole(x, y, d_shaft, d_head=0.0, angle=0, tabs=0)[source]
+

Draw a pear shaped mounting hole for sliding over a screw head. Total height = 1.5* d_shaft + d_head

+
+
Parameters:
+
    +
  • x – position

  • +
  • y – postion

  • +
  • d_shaft – diameter of the screw shaft

  • +
  • d_head – diameter of the screw head

  • +
  • angle – rotation angle of the hole

  • +
+
+
+
+ +
+
+move(x, y, where, before=False, label='')[source]
+

Intended to be used by parts +where can be combinations of “up” or “down”, “left” or “right”, “only”, +“mirror” and “rotated” +when “only” is included the move is only done when before is True +“mirror” will flip the part along the y axis +“rotated” draws the parts rotated 90 counter clockwise +The function returns whether actual drawing of the part +should be ommited.

+
+
Parameters:
+
    +
  • x – width of part

  • +
  • y – height of part

  • +
  • where – which direction to move

  • +
  • before – (Default value = False) called before or after part being drawn

  • +
+
+
+
+ +
+
+moveArc(angle, r=0.0)[source]
+
+
Parameters:
+
    +
  • angle

  • +
  • r – (Default value = 0.0)

  • +
+
+
+
+ +
+
+moveTo(x, y=0.0, degrees=0)[source]
+

Move coordinate system to given point

+
+
Parameters:
+
    +
  • x

  • +
  • y – (Default value = 0.0)

  • +
  • degrees – (Default value = 0)

  • +
+
+
+
+ +
+
+nema_sizes = {8: (20.3, 16, 15.4, 3), 11: (28.2, 22, 23, 4), 14: (35.2, 22, 26, 4), 16: (39.2, 22, 31, 4), 17: (42.2, 22, 31, 4), 23: (56.4, 38.1, 47.1, 5.2), 24: (60, 36, 49.8, 5.1), 34: (86.3, 73, 69.8, 6.6), 42: (110, 55.5, 89, 8.5)}
+
+ +
+
+open()[source]
+

Prepare for rendering

+

Create canvas and edge and other objects +Call this before .render()

+
+ +
+
+parseArgs(args=None)[source]
+

Parse command line parameters

+
+
Parameters:
+

args – (Default value = None) parameters, None for using sys.argv

+
+
+
+ +
+
+partsMatrix(n, width, move, part, *l, **kw)[source]
+

place many of the same part

+
+
Parameters:
+
    +
  • n – number of parts

  • +
  • width – number of parts in a row (0 for same as n)

  • +
  • move – (Default value = None)

  • +
  • part – callable that draws a part and knows move param

  • +
  • *l – params for part

  • +
  • **kw – keyword params for part

  • +
+
+
+
+ +
+
+polygonWall(borders, edge='f', turtle=False, callback=None, move=None, label='')[source]
+

Polygon wall for all kind of multi-edged objects

+
+
Parameters:
+
    +
  • borders – array of distance and angles to draw

  • +
  • edge – (Default value = “f”) Edges to apply. If the array of borders contains more segments that edges, the edge will wrap. Only edge types without start and end width suppported for now.

  • +
  • turtle – (Default value = False)

  • +
  • callback – (Default value = None)

  • +
  • move – (Default value = None)

  • +
  • label – rendered to identify parts, it is not ment to be cut or etched (Default value = “”)

  • +
+
+
+
+ +
+
+polygonWalls(borders, h, bottom='F', top='F', symetrical=True)[source]
+
+ +
+
+polyline(*args)[source]
+

Draw multiple connected lines

+
+
Parameters:
+

*args – Alternating length in mm and angle in degrees.

+
+
+

lengths may be a tuple (length, #tabs) +angles may be tuple (angle, radius)

+
+ +
+
+rectangularHole(x, y, dx, dy, r=0, center_x=True, center_y=True)[source]
+

Draw a rectangular hole

+
+
Parameters:
+
    +
  • x – position

  • +
  • y – position

  • +
  • dx – width

  • +
  • dy – height

  • +
  • r – (Default value = 0) radius of the corners

  • +
  • center_x – (Default value = True) if True, x position is the center, else the start

  • +
  • center_y – (Default value = True) if True, y position is the center, else the start

  • +
+
+
+
+ +
+
+rectangularTriangle(x, y, edges='eee', r=0.0, num=1, bedBolts=None, bedBoltSettings=None, callback=None, move=None, label='')[source]
+

Rectangular triangular wall

+
+
Parameters:
+
    +
  • x – width

  • +
  • y – height

  • +
  • edges – (Default value = “eee”) bottom, right[, diagonal]

  • +
  • r – radius towards the hypothenuse

  • +
  • num – (Default value = 1) number of triangles

  • +
  • bedBolts – (Default value = None)

  • +
  • bedBoltSettings – (Default value = None)

  • +
  • callback – (Default value = None)

  • +
  • move – (Default value = None)

  • +
  • label – rendered to identify parts, it is not ment to be cut or etched (Default value = “”)

  • +
+
+
+
+ +
+
+rectangularWall(x, y, edges='eeee', ignore_widths=[], holesMargin=None, holesSettings=None, bedBolts=None, bedBoltSettings=None, callback=None, move=None, label='')[source]
+

Rectangular wall for all kind of box like objects

+
+
Parameters:
+
    +
  • x – width

  • +
  • y – height

  • +
  • edges – (Default value = “eeee”) bottom, right, top, left

  • +
  • ignore_widths – list of edge_widths added to adjacent edge

  • +
  • holesMargin – (Default value = None)

  • +
  • holesSettings – (Default value = None)

  • +
  • bedBolts – (Default value = None)

  • +
  • bedBoltSettings – (Default value = None)

  • +
  • callback – (Default value = None)

  • +
  • move – (Default value = None)

  • +
  • label – rendered to identify parts, it is not ment to be cut or etched (Default value = “”)

  • +
+
+
+
+ +
+
+regularPolygon(corners=3, radius=None, h=None, side=None)[source]
+

Give measures of a regular polygon

+
+
Parameters:
+
    +
  • corners – number of corners of the polygon

  • +
  • radius – distance center to one of the corners

  • +
  • h – distance center to one of the sides (height of sector)

  • +
  • side – length of one side

  • +
+
+
Returns:
+

(radius, h, side)

+
+
+
+ +
+
+regularPolygonAt(x, y, corners, angle=0, r=None, h=None, side=None)[source]
+

Draw regular polygon

+
+ +
+
+regularPolygonHole(x, y, r=0.0, d=0.0, n=6, a=0.0, tabs=0, corner_radius=0.0)[source]
+

Draw a hole in shape of an n-edged regular polygon

+
+
Parameters:
+
    +
  • x – position

  • +
  • y – postion

  • +
  • r – radius

  • +
  • n – number of edges

  • +
  • a – rotation angle

  • +
+
+
+
+ +
+
+regularPolygonWall(corners=3, r=None, h=None, side=None, edges='e', hole=None, callback=None, move=None)[source]
+

Create regular polygon as a wall

+
+
Parameters:
+
    +
  • corners – number of corners of the polygon

  • +
  • radius – distance center to one of the corners

  • +
  • h – distance center to one of the sides (height of sector)

  • +
  • side – length of one side

  • +
  • edges – (Default value = “e”, may be string/list of length corners)

  • +
  • hole – diameter of central hole (Default value = 0)

  • +
  • callback – (Default value = None, middle=0, then sides=1..)

  • +
  • move – (Default value = None)

  • +
+
+
+
+ +
+
+render()[source]
+

Implement this method in your sub class.

+

You will typically need to call .parseArgs() before calling this one

+
+ +
+
+roundedPlate(x, y, r, edge='f', callback=None, holesMargin=None, holesSettings=None, bedBolts=None, bedBoltSettings=None, wallpieces=1, extend_corners=True, move=None)[source]
+

Plate with rounded corner fitting to .surroundingWall()

+

For the callbacks the sides are counted depending on wallpieces

+
+
Parameters:
+
    +
  • x – width

  • +
  • y – height

  • +
  • r – radius of the corners

  • +
  • callback – (Default value = None)

  • +
  • holesMargin – (Default value = None) set to get hex holes

  • +
  • holesSettings – (Default value = None)

  • +
  • bedBolts – (Default value = None)

  • +
  • bedBoltSettings – (Default value = None)

  • +
  • wallpieces – (Default value = 1) # of separate surrounding walls

  • +
  • extend_corners – (Default value = True) have corners outset with the edges

  • +
  • move – (Default value = None)

  • +
+
+
+
+ +
+
+saved_context()[source]
+

Generator: for saving and restoring contexts.

+
+ +
+
+set_font(style, bold=False, italic=False)[source]
+

Set font style used +:param style: “serif”, “sans-serif” or “monospaced” +:param bold: Use bold font +:param italic: Use italic font

+
+ +
+
+set_source_color(color)[source]
+

Sets the color of the pen.

+
+ +
+
+showBorderPoly(border, color=[1.0, 0.0, 0.0])[source]
+

draw border polygon (for debugging only)

+
+
Parameters:
+

border – array with coordinate [(x0,y0), (x1,y1),…] of the border polygon

+
+
+
+ +
+
+step(out)[source]
+

Create a parallel step prependicular to the current direction +Positive values move to the outside of the part

+
+ +
+
+surroundingWall(x, y, r, h, bottom='e', top='e', left='D', right='d', pieces=1, extend_corners=True, callback=None, move=None)[source]
+

Wall(s) with flex fiting around a roundedPlate()

+

For the callbacks the sides are counted depending on pieces

+
+
Parameters:
+
    +
  • x – width of matching roundedPlate

  • +
  • y – height of matching roundedPlate

  • +
  • r – corner radius of matching roundedPlate

  • +
  • h – inner height of the wall (without edges)

  • +
  • bottom – (Default value = ‘e’) Edge type

  • +
  • top – (Default value = ‘e’) Edge type

  • +
  • left – (Default value = ‘D’) left edge(s)

  • +
  • right – (Default value = ‘d’) right edge(s)

  • +
  • pieces – (Default value = 1) number of separate pieces

  • +
  • callback – (Default value = None)

  • +
  • move – (Default value = None)

  • +
+
+
+
+ +
+
+surroundingWallPiece(cbnr, x, y, r, pieces=1)[source]
+

Return the geometry of a pices of surroundingWall with the given +callback number. +:param cbnr: number of the callback corresponding to this part of the wall +:param x: width of matching roundedPlate +:param y: height of matching roundedPlate +:param r: corner radius of matching roundedPlate +:param pieces: (Default value = 1) number of separate pieces +:return: (left, length, right) left and right are Booleans that are True if the start or end of the wall is on that side.

+
+ +
+
+text(text, x=0, y=0, angle=0, align='', fontsize=10, color=[0.0, 0.0, 0.0], font='Arial')[source]
+

Draw text

+
+
Parameters:
+
    +
  • text – text to render

  • +
  • x – (Default value = 0)

  • +
  • y – (Default value = 0)

  • +
  • angle – (Default value = 0)

  • +
  • align – (Default value = “”) string with combinations of (top|middle|bottom) and (left|center|right) separated by a space

  • +
+
+
+
+ +
+
+trapezoidSideWall(w, h0, h1, edges='eeee', radius=0.0, callback=None, move=None, label='')[source]
+

Rectangular trapezoidal wall

+
+
Parameters:
+
    +
  • w – width

  • +
  • h0 – left height

  • +
  • h1 – right height

  • +
  • edges – (Default value = “eeee”) bottom, right, left

  • +
  • radius – (Default value = 0.0) radius of upper corners

  • +
  • callback – (Default value = None)

  • +
  • move – (Default value = None)

  • +
  • label – rendered to identify parts, it is not ment to be cut or etched (Default value = “”)

  • +
+
+
+
+ +
+
+trapezoidWall(w, h0, h1, edges='eeee', callback=None, move=None, label='')[source]
+

Rectangular trapezoidal wall

+
+
Parameters:
+
    +
  • w – width

  • +
  • h0 – left height

  • +
  • h1 – right height

  • +
  • edges – (Default value = “eee”) bottom, right, left

  • +
  • callback – (Default value = None)

  • +
  • move – (Default value = None)

  • +
  • label – rendered to identify parts, it is not ment to be cut or etched (Default value = “”)

  • +
+
+
+
+ +
+
+tx_sizes = {1: 0.61, 2: 0.7, 3: 0.82, 4: 0.96, 5: 1.06, 6: 1.27, 7: 1.49, 8: 1.75, 9: 1.87, 10: 2.05, 15: 2.4, 20: 2.85, 25: 3.25, 30: 4.05, 40: 4.85, 45: 5.64, 50: 6.45, 55: 8.05, 60: 9.6, 70: 11.2, 80: 12.8, 90: 14.4, 100: 16.0}
+
+ +
+
+ui_group = 'Misc'
+
+ +
+
+webinterface = True
+
+ +
+ +
+
+class boxes.HexHolesSettings(thickness, relative=True, **kw)[source]
+

Bases: Settings

+

Settings for hexagonal hole patterns

+

Values:

+
    +
  • absolute +* diameter : 5.0 : diameter of the holes +* distance : 3.0 : distance between the holes +* style : “circle” : currently only supported style

  • +
+
+
+absolute_params = {'diameter': 10.0, 'distance': 3.0, 'style': ('circle',)}
+
+ +
+
+relative_params = {}
+
+ +
+ +
+
+class boxes.NutHole(boxes, settings)[source]
+

Bases: object

+

Draw a hex nut

+
+
+sizes = {'M1.6': (3.2, 1.3), 'M10': (16, 8.4), 'M12': (18, 10.8), 'M14': (21, 12.8), 'M16': (24, 14.8), 'M2': (4, 1.6), 'M2.5': (5, 2.0), 'M20': (30, 18.0), 'M24': (36, 21.5), 'M3': (5.5, 2.4), 'M30': (46, 25.6), 'M36': (55, 31), 'M4': (7, 3.2), 'M42': (65, 34), 'M48': (75, 38), 'M5': (8, 4.7), 'M56': (85, 45), 'M6': (10, 5.2), 'M64': (95, 51), 'M8': (13.7, 6.8)}
+
+ +
+ +
+
+boxes.argparseSections(s)[source]
+

Parse sections parameter

+
+
Parameters:
+

s – string to parse

+
+
+
+ +
+
+boxes.dist(dx, dy)[source]
+

Return distance

+
+
Parameters:
+
    +
  • dx – delta x

  • +
  • dy – delat y

  • +
+
+
+
+ +
+
+class boxes.fillHolesSettings(thickness, relative=True, **kw)[source]
+

Bases: Settings

+

Settings for Hole filling

+

Values:

+
    +
  • absolute +* fill_pattern : “no fill” : style of hole pattern +* hole_style : “round” : style of holes (does not apply to fill patterns ‘vbar’ and ‘hbar’) +* max_random : 1000 : maximum number of random holes +* bar_length : 50 : maximum length of bars +* hole_max_radius : 12.0 : maximum radius of generated holes (in mm) +* hole_min_radius : 4.0 : minimum radius of generated holes (in mm) +* space_between_holes : 4.0 : hole to hole spacing (in mm) +* space_to_border : 4.0 : hole to border spacing (in mm)

  • +
+
+
+absolute_params = {'bar_length': 50, 'fill_pattern': ('no fill', 'hex', 'square', 'random', 'hbar', 'vbar'), 'hole_max_radius': 3.0, 'hole_min_radius': 0.5, 'hole_style': ('round', 'triangle', 'square', 'hexagon', 'octagon'), 'max_random': 1000, 'space_between_holes': 4.0, 'space_to_border': 4.0}
+
+ +
+ +
+
+boxes.holeCol(func)[source]
+

Wrapper: color holes differently

+
+
Parameters:
+

func – function to wrap

+
+
+
+ +
+
+boxes.restore(func)[source]
+

Wrapper: Restore coordiantes after function

+
+
Parameters:
+

func – function to wrap

+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/faq.html b/html/faq.html new file mode 100644 index 0000000..b2f2c88 --- /dev/null +++ b/html/faq.html @@ -0,0 +1,175 @@ + + + + + + + + + Frequently Asked Questions — boxes.py 0.1 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Frequently Asked Questions

+
+
+
+

Can I sell boxes I created with Boxes.py

+

Yes. Boxes.py is under the GPLv3 license (see https://www.gnu.org/licenses/gpl-3.0.html). This license grants you far reaching rights on what you can do with the software including using it and the drawings it produces to any means. The license also puts some obligations on you. But those are about changing and distributing the software itself. The resulting drawings do not fall under the GPL license.

+
+
+

Why do my parts not fit together?

+

Well, this could be a bug in Boxes.py but there are a few more likely causes to check for:

+
    +
  • The material you use does not have the thickness you think. Measure it with at least a caliper. Even a few hundredth of a milimeter will make the difference between a loose fit, a light or a heavy pressfit or no fit at all.

  • +
  • You might have choosen the “burn” value too big. As it compensates for the material cut away by the laser smaller values make a looser fit, bigger values make a tighter fit. The right value may be different for different materials and different thicknesses.

  • +
+
+
+

Why is my box a bit too big?

+

By default all sizes are inner sizes. So on the outside the box is bigger as the walls need to go somewhere. Some generators offer an “outside” param that includes the walls in the measurements. In general you should check the generated parts for plausability before hitting the start button on your laser cutter.

+
+
+

Why is my box a bit too small?

+

See above.

+
+
+

Why are my parts in the totally wrong size?

+

Unfortunately some formats do not save the units of measurement or don’t do so properly. DXF and SVG fall into this category. So different tools may see the same file in different sizes. You can use the “reference” param to get a rectangle of a defined size to check if the size is still right at the end of your tool chain.

+
+
+

Why are there tiny, weird loops in the corners?

+

These are called dog bones and make sure the corner is completely cut out. As lasers and milling tools are round they can’t cut sharp inner corners. Have a look at burn correction details for details.

+
+
+

I really don’t want those weird, tiny loops?

+

You can set the inner_corners default setting to corner

+
+
+

What settings were used to generate a drawing?

+

If you do have a SVG or PostScript you can look into the meta data of the file. Most document viewers will have a Document properties window. You can also just open the file with a text editor and find the details at the first few lines.

+

Note that you can just use the URL in there to get back to the settings page to change some values. The difference between the settings and the rendered drawing is just render=0 or render=1 at the end of the URL.

+

For other formats you are currently out of luck.

+
+
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/generators.html b/html/generators.html new file mode 100644 index 0000000..2acb646 --- /dev/null +++ b/html/generators.html @@ -0,0 +1,2027 @@ + + + + + + + + + All Box Generators — boxes.py 0.1 documentation + + + + + + + + + + + + + + + +
+
+
+
+ +
+

All Box Generators

+

Generators are organized in several Groups

+ +
+

Box

+
+

ABox

+
+
+class boxes.generators.abox.ABox[source]
+

A simple Box

+
+ +
+
+

AngledBox

+
+
+class boxes.generators.angledbox.AngledBox[source]
+

Box with both ends cornered

+
+ +_images/AngledBox1.jpg +
+
+

BasedBox

+
+
+class boxes.generators.basedbox.BasedBox[source]
+

Fully closed box on a base

+
+ +_images/BasedBox.jpg +
+
+

BayonetBox

+
+
+class boxes.generators.bayonetbox.BayonetBox[source]
+

Round box made from layers with twist on top

+
+ +_images/BayonetBox.jpg +
+
+

CardBox

+
+
+class boxes.generators.cardbox.CardBox[source]
+

Box for storage of playing cards

+
+ +_images/CardBox.jpg +
+
+

ClosedBox

+
+
+class boxes.generators.closedbox.ClosedBox[source]
+

Fully closed box

+
+ +_images/ClosedBox.jpg +
+
+

Console

+
+
+class boxes.generators.console.Console[source]
+

Console with slanted panel

+
+ +_images/Console.jpg +
+
+

Console2

+
+
+class boxes.generators.console2.Console2[source]
+

Console with slanted panel and service hatches

+
+ +_images/Console2.jpg +
+
+

DiceBox

+
+
+class boxes.generators.dicebox.DiceBox[source]
+

Box with lid and integraded hinge for storing dice

+
+ +_images/DiceBox.jpg +
+
+

DisplayCase

+
+
+class boxes.generators.displaycase.DisplayCase[source]
+

Fully closed box intended to be cut from transparent acrylics and to serve as a display case.

+
+ +_images/DisplayCase.jpg +
+
+

ElectronicsBox

+
+
+class boxes.generators.electronicsbox.ElectronicsBox[source]
+

Closed box with screw on top and mounting holes

+
+ +_images/ElectronicsBox.jpg +
+
+

EuroRackSkiff

+
+
+class boxes.generators.eurorackskiff.EuroRackSkiff[source]
+

3U Height case with adjustable width and height and included rails

+
+ +_images/EuroRackSkiff.jpg +
+
+

HalfBox

+
+
+class boxes.generators.halfbox.HalfBox[source]
+

Configurable half of a box which can be: a bookend, a hanging shelf, an angle clamping jig, …

+
+ +_images/HalfBox.jpg +
+
+

HingeBox

+
+
+class boxes.generators.hingebox.HingeBox[source]
+

Box with lid attached by cabinet hinges

+
+ +_images/HingeBox1.jpg +
+
+

IntegratedHingeBox

+
+
+class boxes.generators.integratedhingebox.IntegratedHingeBox[source]
+

Box with lid and integraded hinge

+
+ +_images/IntegratedHingeBox.jpg +
+
+

Keypad

+
+
+class boxes.generators.keypad.Keypad[source]
+

Generator for keypads with mechanical switches.

+
+ +
+
+

NotesHolder

+
+
+class boxes.generators.notesholder.NotesHolder[source]
+

Box for holding a stack of paper, coasters etc

+
+ +_images/NotesHolder1.jpg +
+
+

OpenBox

+
+
+class boxes.generators.openbox.OpenBox[source]
+

Box with top and front open

+
+ +_images/OpenBox.jpg +
+
+

Rack10Box

+
+
+class boxes.generators.rack10box.Rack10Box[source]
+

Closed box with screw on top for mounting in a 10” rack.

+
+ +
+
+

Rack19Box

+
+
+class boxes.generators.rack19box.Rack19Box[source]
+

Closed box with screw on top for mounting in a 19” rack.

+
+ +
+
+

Rack19HalfWidth

+
+
+class boxes.generators.rack19halfwidth.Rack19HalfWidth[source]
+

Half width 19inch rack unit for musical equipment.

+
+ +
+
+

RackBox

+
+
+class boxes.generators.rackbox.RackBox[source]
+

Closed box with screw on top and mounting holes

+
+ +
+
+

RegularBox

+
+
+class boxes.generators.regularbox.RegularBox[source]
+

Box with regular polygon as base

+
+ +_images/RegularBox.jpg +
+
+

RegularStarBox

+
+
+class boxes.generators.regularstarbox.RegularStarBox[source]
+

Regular polygon boxes that form a star when closed

+
+ +_images/RegularStarBox.jpg +
+
+

SideDoorHousing

+
+
+class boxes.generators.sidedoorhousing.SideDoorHousing[source]
+

A box with service hatches at the sides

+
+ +
+
+

SlidingDrawer

+
+
+class boxes.generators.slidingdrawer.SlidingDrawer[source]
+

Sliding drawer box

+
+ +_images/SlidingDrawer.jpg +
+
+

TwoPiece

+
+
+class boxes.generators.two_piece.TwoPiece[source]
+

A two piece box where top slips over the bottom half to form +the enclosure.

+
+ +_images/TwoPiece1.jpg +
+
+

UnevenHeightBox

+
+
+class boxes.generators.unevenheightbox.UnevenHeightBox[source]
+

Box with different height in each corner

+
+ +_images/UnevenHeightBox.jpg +
+
+

UniversalBox

+
+
+class boxes.generators.universalbox.UniversalBox[source]
+

Box with various options for different styles and lids

+
+ +_images/UniversalBox.jpg +
+
+
+

FlexBox

+
+

DoubleFlexDoorBox

+
+
+class boxes.generators.doubleflexdoorbox.DoubleFlexDoorBox[source]
+

Box with two part lid with living hinges and round corners

+
+ +_images/DoubleFlexDoorBox.jpg +
+
+

FlexBox

+
+
+class boxes.generators.flexbox.FlexBox[source]
+

Box with living hinge and round corners

+
+ +_images/FlexBox.jpg +
+
+

FlexBox2

+
+
+class boxes.generators.flexbox2.FlexBox2[source]
+

Box with living hinge and top corners rounded

+
+ +_images/FlexBox21.jpg +
+
+

FlexBox3

+
+
+class boxes.generators.flexbox3.FlexBox3[source]
+

Box with living hinge

+
+ +_images/FlexBox3.jpg +
+
+

FlexBox4

+
+
+class boxes.generators.flexbox4.FlexBox4[source]
+

Box with living hinge and left corners rounded

+
+ +_images/FlexBox4.jpg +
+
+

FlexBox5

+
+
+class boxes.generators.flexbox5.FlexBox5[source]
+

Box with living hinge and round corners

+
+ +
+
+

HeartBox

+
+
+class boxes.generators.heart.HeartBox[source]
+

Box in the form of an heart

+
+ +_images/HeartBox1.jpg +
+
+

RoundedBox

+
+
+class boxes.generators.roundedbox.RoundedBox[source]
+

Box with vertical edges rounded

+
+ +_images/RoundedBox.jpg +
+
+

ShutterBox

+
+
+class boxes.generators.shutterbox.ShutterBox[source]
+

Box with a rolling shutter made of flex

+
+ +_images/ShutterBox1.jpg +
+
+

UBox

+
+
+class boxes.generators.ubox.UBox[source]
+

Box various options for different stypes and lids

+
+ +_images/UBox.jpg +
+
+
+

Tray

+
+

DividerTray

+
+
+class boxes.generators.dividertray.DividerTray[source]
+

Divider tray - rows and dividers

+
+ +_images/DividerTray.jpg +
+
+

DrillBox

+
+
+class boxes.generators.drillbox.DrillBox[source]
+

A parametrized box for drills

+
+ +
+
+

TrayInsert

+
+
+class boxes.generators.trayinsert.TrayInsert[source]
+

Tray insert without floor and outer walls - allows only continuous walls

+
+ +_images/TrayInsert.jpg +
+
+

TrayLayout

+
+
+class boxes.generators.traylayout.TrayLayout(input=None, webargs=False)[source]
+

Generate a typetray from a layout file

+
+ +_images/TrayLayout.jpg +
+
+

TrayLayout2

+
+
+class boxes.generators.traylayout.TrayLayout2(input=None, webargs=False)[source]
+

Generate a typetray from a layout file

+
+ +_images/TrayLayout2.jpg +
+
+

TypeTray

+
+
+class boxes.generators.typetray.TypeTray[source]
+

Type tray - allows only continuous walls

+
+ +_images/TypeTray1.jpg +
+
+
+

Shelf

+
+

BinTray

+
+
+class boxes.generators.bintray.BinTray[source]
+

A Type tray variant to be used up right with sloped walls in front

+
+ +_images/BinTray1.jpg +
+
+

CardHolder

+
+
+class boxes.generators.cardholder.CardHolder[source]
+

Shelf for holding (multiple) piles of playing cards / notes

+
+ +_images/CardHolder.jpg +
+
+

DiscRack

+
+
+class boxes.generators.discrack.DiscRack[source]
+

A rack for storing disk-shaped objects vertically next to each other

+
+ +_images/DiscRack.jpg +
+
+

DisplayShelf

+
+
+class boxes.generators.displayshelf.DisplayShelf[source]
+

Shelf with slanted floors

+
+ +_images/DisplayShelf1.jpg +
+
+

PaintStorage

+
+
+class boxes.generators.paintbox.PaintStorage[source]
+

Stackable storage for hobby paint or other things

+
+ +_images/PaintStorage1.jpg +
+
+

SBCMicroRack

+
+
+class boxes.generators.microrack.SBCMicroRack[source]
+

Stackable rackable racks for SBC Pi-Style Computers

+
+ +_images/SBCMicroRack.jpg +
+
+

SpicesRack

+
+
+class boxes.generators.spicesrack.SpicesRack[source]
+

Rack for cans of spices

+
+ +_images/SpicesRack.jpg +
+
+

StorageRack

+
+
+class boxes.generators.storagerack.StorageRack[source]
+

StorageRack to store boxes and trays which have their own floor

+
+ +_images/StorageRack.jpg +
+
+

StorageShelf

+
+
+class boxes.generators.storageshelf.StorageShelf[source]
+

StorageShelf can be used to store Typetray

+
+ +_images/StorageShelf.jpg +
+
+

WineRack

+
+
+class boxes.generators.winerack.WineRack[source]
+

Honey Comb Style Wine Rack

+
+ +_images/WineRack.jpg +
+
+
+

WallMounted

+
+

DinRailBox

+
+
+class boxes.generators.dinrailbox.DinRailBox[source]
+

Box for DIN rail used in electrical junction boxes

+
+ +
+
+

WallCaliper

+
+
+class boxes.generators.wallcaliperholder.WallCaliper[source]
+

Holds a single caliper to a wall

+
+ +_images/WallCaliper.jpg +
+
+

WallChiselHolder

+
+
+class boxes.generators.wallchiselholder.WallChiselHolder[source]
+

Wall tool holder for chisels, files and similar tools

+
+ +
+
+

WallConsole

+
+
+class boxes.generators.wallconsole.WallConsole[source]
+

Outset and angled plate to mount stuff to

+
+ +
+
+

WallDrillBox

+
+
+class boxes.generators.walldrillbox.WallDrillBox[source]
+

Box for drills with each compartment with a different height

+
+ +_images/WallDrillBox.jpg +
+
+

WallEdges

+
+
+class boxes.generators.walledges.WallEdges[source]
+

Shows the different edge types for wall systems

+
+ +
+
+

WallPinRow

+
+
+class boxes.generators.wallpinrow.WallPinRow[source]
+

Outset and angled plate to mount stuff to

+
+ +
+
+

WallPlaneHolder

+
+
+class boxes.generators.wallplaneholder.WallPlaneHolder[source]
+

Hold a plane to a wall

+
+ +
+
+

WallPliersHolder

+
+
+class boxes.generators.wallpliersholder.WallPliersHolder[source]
+

Bar to hang pliers on

+
+ +_images/WallPliersHolder.jpg +
+
+

WallSlottedHolder

+
+
+class boxes.generators.wallslottedholder.WallSlottedHolder[source]
+

Wall tool holder with slots

+
+ +_images/WallSlottedHolder.jpg +
+
+

WallStairs

+
+
+class boxes.generators.wallstairs.WallStairs[source]
+

Platforms in different heights e.g. for screw drivers

+
+ +
+
+

WallTypeTray

+
+
+class boxes.generators.walltypetray.WallTypeTray[source]
+

Type tray - allows only continuous walls

+
+ +_images/WallTypeTray.jpg +
+
+

WallWrenchHolder

+
+
+class boxes.generators.wallwrenchholder.WallWrenchHolder[source]
+

Hold a set of wrenches at a wall

+
+ +
+
+
+

Holes

+
+

FanHole

+
+
+class boxes.generators.fanhole.FanHole[source]
+

Hole pattern for mounting a fan

+
+ +
+
+

HolePattern

+
+
+class boxes.generators.holepattern.HolePattern[source]
+

Generate hole patterns in different simple shapes

+
+ +
+
+

NemaPattern

+
+
+class boxes.generators.nemapattern.NemaPattern[source]
+

Mounting holes for a Nema motor

+
+ +
+
+
+

Part

+
+

BurnTest

+
+
+class boxes.generators.burntest.BurnTest[source]
+

Test different burn values

+
+ +_images/BurnTest.jpg +
+
+

ConcaveKnob

+
+
+class boxes.generators.concaveknob.ConcaveKnob[source]
+

Round knob serrated outside for better gripping

+
+ +
+
+

FillTest

+
+
+class boxes.generators.filltest.FillTest[source]
+

Piece for testing different settings for hole filling

+
+ +
+
+

FlexTest

+
+
+class boxes.generators.flextest.FlexTest[source]
+

Piece for testing different flex settings

+
+ +_images/FlexTest.jpg +
+
+

FlexTest2

+
+
+class boxes.generators.flextest2.FlexTest2[source]
+

Piece for testing 2D flex settings

+
+ +
+
+

GearBox

+
+
+class boxes.generators.gearbox.GearBox[source]
+

Gearbox with multiple identical stages

+
+ +
+
+

Gears

+
+
+class boxes.generators.gear.Gears[source]
+
+ +
+
+

LBeam

+
+
+class boxes.generators.lbeam.LBeam[source]
+

Simple L-Beam: two pieces joined with a right angle

+
+ +_images/LBeam.jpg +
+
+

LaserHoldfast

+
+
+class boxes.generators.laserholdfast.LaserHoldfast[source]
+

A holdfast for honey comb tables of laser cutters

+
+ +_images/LaserHoldfast.jpg +
+
+

NemaMount

+
+
+class boxes.generators.nemamount.NemaMount[source]
+

Mounting braket for a Nema motor

+
+ +
+
+

Planetary

+
+
+class boxes.generators.planetary.Planetary[source]
+

Planetary Gear with possibly multiple identical stages

+
+ +
+
+

Pulley

+
+
+class boxes.generators.pulley.Pulley[source]
+

Timing belt pulleys for different profiles

+
+ +
+
+

RectangularWall

+
+
+class boxes.generators.rectangularWall.RectangularWall[source]
+

Simple wall

+
+ +
+
+

RobotArm

+
+
+class boxes.generators.robotarm.RobotArm[source]
+

Segments of servo powered robot arm

+
+ +_images/RobotArm.jpg +
+
+

WaivyKnob

+
+
+class boxes.generators.waivyknob.WaivyKnob[source]
+

Round knob serrated outside for better gripping

+
+ +
+
+
+

Misc

+
+

AgricolaInsert

+
+
+class boxes.generators.agricolainsert.AgricolaInsert[source]
+

Agricola Revised Edition game box insert, including some expansions.

+
+ +_images/AgricolaInsert1.jpg +
+
+

AllEdges

+
+
+class boxes.generators.alledges.AllEdges[source]
+

Showing all edge types

+
+ +_images/AllEdges.jpg +
+
+

AngledCutJig

+
+
+class boxes.generators.angledcutjig.AngledCutJig[source]
+

Jig for making angled cuts in a laser cutter

+
+ +_images/AngledCutJig.jpg +
+
+

Arcade

+
+
+class boxes.generators.arcade.Arcade[source]
+

Desktop Arcade Machine

+
+ +
+
+

Atreus21

+
+
+class boxes.generators.atreus21.Atreus21[source]
+

Generator for a split atreus keyboard.

+
+ +_images/Atreus211.jpg +
+
+

BottleStack

+
+
+class boxes.generators.bottlestack.BottleStack[source]
+

Stack bottles in a fridge

+
+ +
+
+

BottleTag

+
+
+class boxes.generators.bottletag.BottleTag[source]
+

Paper slip over bottle tag

+
+ +_images/BottleTag.jpg +
+
+

CanStorage

+
+
+class boxes.generators.can_storage.CanStorage[source]
+

Storage box for round containers

+
+ +_images/CanStorage.jpg +
+
+

CoffeeCapsuleHolder

+
+
+class boxes.generators.coffeecapsulesholder.CoffeeCapsuleHolder[source]
+

Coffee capsule holder

+
+ +_images/CoffeeCapsuleHolder.jpg +
+
+

CoinDisplay

+
+
+class boxes.generators.coindisplay.CoinDisplay[source]
+

A showcase for a single coin

+
+ +_images/CoinDisplay.jpg +
+
+

Dispenser

+
+
+class boxes.generators.dispenser.Dispenser[source]
+

Dispenser for stackable (flat) items of same size

+
+ +
+
+

Display

+
+
+class boxes.generators.display.Display[source]
+

Diplay for flyers or leaflets

+
+ +
+
+

DrillStand

+
+
+class boxes.generators.drillstand.DrillStand[source]
+

Box for drills with each compartment of a different height

+
+ +_images/DrillStand.jpg +
+
+

Folder

+
+
+class boxes.generators.folder.Folder[source]
+

Book cover with flex for the spine

+
+ +_images/Folder.jpg +
+
+

Hook

+
+
+class boxes.generators.hooks.Hook[source]
+

A hook wit a rectangular mouth to mount at the wall

+
+ +
+
+

JointPanel

+
+
+class boxes.generators.jointpanel.JointPanel[source]
+

Create pieces larger than your laser cutter by joining them with Dove Tails

+
+ +_images/JointPanel.jpg +
+
+

LaptopStand

+
+
+class boxes.generators.laptopstand.LaptopStand[source]
+

A simple X shaped frame to support a laptop on a given angle

+
+ +
+
+

LaserClamp

+
+
+class boxes.generators.laserclamp.LaserClamp[source]
+

A clamp to hold down material to a knife table

+
+ +_images/LaserClamp.jpg +
+
+

MagazinFile

+
+
+class boxes.generators.magazinefile.MagazinFile[source]
+

Open magazine file

+
+ +_images/MagazinFile.jpg +
+
+

MakitaPowerSupply

+
+
+class boxes.generators.makitapowersupply.MakitaPowerSupply[source]
+

Bench power supply powered with Maktia 18V battery or laptop power supply

+
+ +_images/MakitaPowerSupply.jpg +
+
+

OttoBody

+
+
+class boxes.generators.ottobody.OttoBody[source]
+

Otto LC - a laser cut chassis for Otto DIY - body

+
+ +_images/OttoBody1.jpg +
+
+

OttoLegs

+
+
+class boxes.generators.ottolegs.OttoLegs[source]
+

Otto LC - a laser cut chassis for Otto DIY - legs

+
+ +
+
+

OttoSoles

+
+
+class boxes.generators.ottosoles.OttoSoles[source]
+

Foam soles for the OttO bot

+
+ +
+
+

PaperBox

+
+
+class boxes.generators.paperbox.PaperBox[source]
+

Box made of paper, with lid.

+
+ +_images/PaperBox.jpg +
+
+

PhoneHolder

+
+
+class boxes.generators.phoneholder.PhoneHolder[source]
+

Smartphone desk holder

+
+ +_images/PhoneHolder.jpg +
+
+

PoleHook

+
+
+class boxes.generators.polehook.PoleHook[source]
+

Hook for pole like things to be clamped to another pole

+
+ +
+
+

RoyalGame

+
+
+class boxes.generators.royalgame.RoyalGame[source]
+

The Royal Game of Ur

+
+ +_images/RoyalGame.jpg +
+
+

Stachel

+
+
+class boxes.generators.stachel.Stachel[source]
+

Bass Recorder Endpin

+
+ +_images/Stachel.jpg +
+
+

TrafficLight

+
+
+class boxes.generators.trafficlight.TrafficLight[source]
+

Traffic light

+
+ +_images/TrafficLight.jpg +
+
+

TriangleLamp

+
+
+class boxes.generators.trianglelamp.TriangleLamp[source]
+

Triangle LED Lamp

+
+ +
+
+
+

Unstable

+
+

BirdHouse

+
+
+class boxes.generators.birdhouse.BirdHouse[source]
+

Simple Bird House

+
+ +
+
+

BreadBox

+
+
+class boxes.generators.breadbox.BreadBox[source]
+

A BreadBox with a gliding door

+
+ +
+
+

Castle

+
+
+class boxes.generators.castle.Castle[source]
+

Castle tower with two walls

+
+ +_images/Castle.jpg +
+
+

OrganPipe

+
+
+class boxes.generators.organpipe.OrganPipe[source]
+

Rectangular organ pipe based on pipecalc

+
+ +
+
+

Planetary2

+
+
+class boxes.generators.planetary2.Planetary2[source]
+

Balanced force Difference Planetary Gear (not yet working properly)

+
+ +
+
+

Platonic

+
+
+class boxes.generators.platonic.Platonic[source]
+

Platonic solids generator

+
+ +_images/Platonic.jpg +
+
+

Rotary

+
+
+class boxes.generators.rotary.Rotary[source]
+

Rotary Attachment for engraving cylindrical objects in a laser cutter

+
+ +_images/Rotary.jpg +
+
+

Silverware

+
+
+class boxes.generators.silverwarebox.Silverware[source]
+

Cuttlery stand with carrying grip +using flex for rounded corners

+
+ +_images/Silverware.jpg +
+
+
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/genindex.html b/html/genindex.html new file mode 100644 index 0000000..805901e --- /dev/null +++ b/html/genindex.html @@ -0,0 +1,1777 @@ + + + + + + + + Index — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ + +

Index

+ +
+ _ + | A + | B + | C + | D + | E + | F + | G + | H + | I + | J + | K + | L + | M + | N + | O + | P + | R + | S + | T + | U + | V + | W + | Y + +
+

_

+ + + +
+ +

A

+ + + +
+ +

B

+ + + +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + + +
+ +

F

+ + + +
+ +

G

+ + + +
+ +

H

+ + + +
+ +

I

+ + + +
+ +

J

+ + +
+ +

K

+ + + +
+ +

L

+ + + +
+ +

M

+ + + +
+ +

N

+ + + +
+ +

O

+ + + +
+ +

P

+ + + +
+ +

R

+ + + +
+ +

S

+ + + +
+ +

T

+ + + +
+ +

U

+ + + +
+ +

V

+ + + +
+ +

W

+ + + +
+ +

Y

+ + +
+ + + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..0542995 --- /dev/null +++ b/html/index.html @@ -0,0 +1,135 @@ + + + + + + + + + Boxes.py — boxes.py 0.1 documentation + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Boxes.py

+

Create boxes and more with a laser cutter!

+

Contents:

+ +
+
+

Indices and tables

+ +
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/install.html b/html/install.html new file mode 100644 index 0000000..e326620 --- /dev/null +++ b/html/install.html @@ -0,0 +1,242 @@ + + + + + + + + + Installation — boxes.py 0.1 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Installation

+

Boxes.py is a pure Python project that does support the regular setuptools +method of shipping with setup.py. setup.py --help-commands and +setup.py CMD --help provide the necessary documentation for building, +installing or building binary formats.

+
+

Requirements

+
+

Affine

+

Affine (package name may be python-affine or +python3-affine) is used for vector calculation.

+
+
+

Shapely

+

shapely (package name may be python-shapely or +python3-shapely) is used for filling shapes (with holes).

+
+
+

Markdown

+

Markdown (package name may be python-markdown or +python3-markdown) is used to format the description texts.

+
+
+

setuptools

+

Setup.py uses the setuptools library (package name may be +python*-setuptools). You only need it if you want to build the +package.

+
+
+

ps2edit

+

While not a hard requirement Boxes.py uses ps2edit to offer formats +that are not supported by Cairo: DXF, gcode, PLT. Currently the location +Boxes.py looks for ps2edit is hard coded to /usr/bin/pstoedit +in the boxes.formats.Formats class.

+
+
+

Python

+

Boxes.py is implemented in Python 3. It used to work on Python 2.7, +too. But with the Python 2 approaching end of life support has been dropped.

+
+
+

Sphinx

+

For building the documentation locally you need the Sphinx documentation +generator (package name may be python-sphinx or python3-sphinx). It is +not needed for anything else. Boxes.py can be run and changed just +fine without.

+
+
+
+

Running from working dir

+

Due to lazy developer(s) Boxes.py can also run from the Git checkout. +The scripts in scripts/ are all suppossed to just work right +after git clone. The Inkscape needs a bit manual work to get +running. See below.

+
+
+

Inkscape

+

As binary

+

Boxes.py can be used as a set of Inkscape plugins. The package does +install the necessary .inx files to /usr/share/inkscape/extensions +on unix operating systems. The .inx files assume that the boxes +executable is available in the path (which it is when installing the +binary package)

+

git repository easy way

+

After cloning it may be most convenient to generate the .inx files +right in place by executing scripts/boxes2inkscape with the taget +path as only parameter.

+
    +
  • global: scripts/boxes2inkscape /usr/share/inkscape/extensions/

  • +
  • userspace: scripts/boxes2inkscape ~/.config/inkscape/extensions/

  • +
+

On non unix operating the target directories may differ. You can look +up the directories “User extensions” and “Inkscape extensions” within +the Inkscape preferences Edit -> Preferences… -> System.

+

git repository manual way

+

setup.py build creates the *.inx files in the inkex/ directory.

+

They then have to be copied in either the global or the per user +extension directory of Inkscape. These are +/usr/share/inkscape/extensions/ and +~/.config/inkscape/extensions/ on a unix operating system. +On non unix operating the target directories may differ. You can look +up the directories “User extensions” and “Inkscape extensions” within +the Inkscape preferences Edit -> Preferences… -> System.

+

As an alternative you can create a symlink to the inkex/ directory +within the desired inkscape extension directory.

+
+
+

Platform specific instructions

+ +
+
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/install/macos.html b/html/install/macos.html new file mode 100644 index 0000000..d5529fe --- /dev/null +++ b/html/install/macos.html @@ -0,0 +1,235 @@ + + + + + + + + + macOS — boxes.py 0.1 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

macOS

+

It is recommended to use Homebrew to install the dependencies for Boxes.py. +See brew.sh on how to install Homebrew.

+
+

General

+
    +
  1. Install Python 3 and other dependencies:

    +
    brew install python3 git
    +
    +
    +

    Optional:

    +
    brew install pstoedit
    +
    +
    +
  2. +
  3. Install cairio:

  4. +
+
+
brew install pkg-config
+
+
+
+
    +
  1. Install required Python modules:

    +
    pip3 install Markdown affine shapely
    +
    +
    +
  2. +
  3. Download Boxes.py via Git:

    +
    git clone https://github.com/florianfesti/boxes.git
    +
    +
    +
  4. +
  5. Run Boxes.py:

    +

    Local web server on port 8000:

    +
    ./scripts/boxesserver
    +
    +
    +

    Command line variant (CLI):

    +
    ./scripts/boxes
    +
    +
    +
  6. +
+
+
+

System-wide with Inkscape extension

+

To install Boxes.py system-wide with the Inkscape extension, following steps +are required:

+
    +
  1. Install Inkscape with Homebrew Cask +(requires XQuartz):

    +
    brew install inkscape
    +
    +
    +
  2. +
  3. From the root directory of the repository, run:

    +
    ./setup.py install
    +
    +
    +
  4. +
  5. Now boxes and boxesserver can be executed like other commands +and the Inkscape extension should be available.

  6. +
+
+

Troubleshooting

+

When using the Inkscape extension something like the following error +might occur:

+
Traceback (most recent call last):
+  File "/Users/martin/.config/inkscape/extensions/boxes", line 107, in <module>
+    main()
+  File "/Users/martin/.config/inkscape/extensions/boxes", line 47, in main
+    run_generator(name, sys.argv[2:])
+  File "/Users/martin/.config/inkscape/extensions/boxes", line 73, in run_generator
+    box.close()
+  File "/usr/local/lib/python3.7/site-packages/boxes-0.1-py3.7.egg/boxes/__init__.py", line 594, in close
+    svgutil.svgMerge(self.output, self.inkscapefile, out)
+  File "/usr/local/lib/python3.7/site-packages/boxes-0.1-py3.7.egg/boxes/svgutil.py", line 144, in svgMerge
+    from lxml import etree as et
+ImportError: dlopen(/Applications/Inkscape.app/Contents/Resources/lib/python2.7/site-packages/lxml/etree.so, 2): Symbol not found: _PyBaseString_Type
+  Referenced from: /Applications/Inkscape.app/Contents/Resources/lib/python2.7/site-packages/lxml/etree.so
+  Expected in: flat namespace
+
+
+

This is because Inkscape on macOS ships its own version of Python 2.7 where +lxml and other dependencies are missing.

+

A workaround is to edit the file at +/Applications/Inkscape.app/Contents/Resources/bin/inkscape. +At line 79 there should be following code:

+
export PYTHONPATH="$TOP/lib/python$PYTHON_VERS/site-packages/"
+
+
+

which needs to be changed to

+
#export PYTHONPATH="$TOP/lib/python$PYTHON_VERS/site-packages/"
+
+
+

This forces Inkscape to use the Python version installed by Homebrew which +has all the necessary dependencies installed.

+

Note: This might break other extensions. In this case simply change the line +back and restart Inkscape.

+
+
+
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/install/windows.html b/html/install/windows.html new file mode 100644 index 0000000..cea4f1d --- /dev/null +++ b/html/install/windows.html @@ -0,0 +1,209 @@ + + + + + + + + + Windows — boxes.py 0.1 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Windows

+

Getting the Inkscape plugins to run will likely need manual +installation (see above). Note that Inkscape may come with its own +Python. If you run into trouble or have better installation +instructions please open a ticket on GitHub.

+
+

Native

+

Following steps are known to work under Windows 10 (64-bit):

+
    +
  1. Go to https://www.python.org/downloads/windows/ +and download the “Windows x86-64 executable installer” for Python 3.7

    +
    +Screenshot of python.org with download of Python 3.7 (64-bit) +
    +
  2. +
  3. Install Python 3.7 and make sure to check “Add Python 3.7 to PATH” +while doing so

    +
    +Screenshot of Python 3.7 (64-bit) installer with PATH checked +
    +
  4. +
  5. Run the command pip install Markdown affine shapely +(Note: If the command pip is not found, you probably forgot to add the +Python installation to the PATH environment variable in step 2)

  6. +
  7. Download Boxes.py as ZIP archive from GitHub

    +
    +Screenshot of download from Boxes.py project on GitHub +
    +
  8. +
  9. Extract the ZIP archive +(e.g. via the built-in Windows feature or other tools like 7-Zip)

    +
    +Screenshot of Windows tools to extract the ZIP archive +
    +
  10. +
  11. +
    Change into the folder for Boxes.py,

    e.g. with the command cd Users[USERNAME]Downloadsboxes-master

    +
    +
    +
  12. +
  13. +
    Run the development server with the command

    python scriptsboxesserver +Note: You likely will be notified by your firewall that it blocked network +access. If you want to use boxesserver you need to allow connections.

    +
    +Screenshot of command for running boxesserver and firewall notice +
    +
    +
    +
  14. +
  15. Open the address http://localhost:8000/ in your browser and have fun :)

    +
    +
    +Screenshot of a browser window running Boxes.py locally +
    +
    +
  16. +
+

Additionally the command line version of Boxes.py can be used with +the command python scriptsboxes.

+
+
+

Windows Subsystem for Linux

+

Another way of installing Boxes.py on Windows is to use the Windows Subsystem +for Linux (WSL). This requires newer versions of Windows 10. Once it is +installed (e.g. via the Ubuntu App from the Microsoft Store), the installation +is identical to the installation on Linux systems.

+

Once wsl is installed, run it and enter the following commands:

+
    +
  • cd ~

  • +
  • git clone https://github.com/florianfesti/boxes.git

  • +
  • cd ~/boxes

  • +
  • python3 -m pip install -r ~/boxes/requirements.txt

  • +
  • python3 ~/boxes/scripts/boxesserver

  • +
+
+Screenshot of a browser window running Boxes.py locally on WSL +
+
+
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/modules.html b/html/modules.html new file mode 100644 index 0000000..fbb05c6 --- /dev/null +++ b/html/modules.html @@ -0,0 +1,804 @@ + + + + + + + + + boxes — boxes.py 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +
+

boxes

+
+ +
+
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/objects.inv b/html/objects.inv new file mode 100644 index 0000000..c33578a Binary files /dev/null and b/html/objects.inv differ diff --git a/html/py-modindex.html b/html/py-modindex.html new file mode 100644 index 0000000..b1b87b2 --- /dev/null +++ b/html/py-modindex.html @@ -0,0 +1,164 @@ + + + + + + + + Python Module Index — boxes.py 0.1 documentation + + + + + + + + + + + + + + + + + +
+
+
+
+ + +

Python Module Index

+ +
+ b +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ b
+ boxes +
    + boxes.Color +
    + boxes.edges +
    + boxes.formats +
    + boxes.gears +
    + boxes.generators +
    + boxes.lids +
    + boxes.parts +
    + boxes.pulley +
    + boxes.robot +
    + boxes.servos +
    + boxes.svgutil +
    + boxes.vectors +
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/search.html b/html/search.html new file mode 100644 index 0000000..ad98907 --- /dev/null +++ b/html/search.html @@ -0,0 +1,108 @@ + + + + + + + + Search — boxes.py 0.1 documentation + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +

Search

+ + + + +

+ Searching for multiple words only shows matches that contain + all words. +

+ + +
+ + + +
+ + + +
+ +
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/html/searchindex.js b/html/searchindex.js new file mode 100644 index 0000000..18c54c8 --- /dev/null +++ b/html/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"docnames": ["CONTRIBUTING", "README", "api_architecture", "api_arguments", "api_burn", "api_drawing", "api_edges", "api_examples", "api_existing_parts", "api_generator", "api_navigation", "api_parts", "apidoc", "boxes", "faq", "generators", "index", "install", "install/macos", "install/windows", "modules", "usermanual"], "filenames": ["CONTRIBUTING.rst", "README.rst", "api_architecture.rst", "api_arguments.rst", "api_burn.rst", "api_drawing.rst", "api_edges.rst", "api_examples.rst", "api_existing_parts.rst", "api_generator.rst", "api_navigation.rst", "api_parts.rst", "apidoc.rst", "boxes.rst", "faq.rst", "generators.rst", "index.rst", "install.rst", "install/macos.rst", "install/windows.rst", "modules.rst", "usermanual.rst"], "titles": ["Contributing to Boxes.py", "About Boxes.py", "Architecture", "Generator Arguments", "Burn correction", "Drawing commands", "Edges", "Examples", "Existing Parts", "Generators", "Navigation", "Parts", "Using the Boxes.py API", "boxes package", "Frequently Asked Questions", "All Box Generators", "Boxes.py", "Installation", "macOS", "Windows", "boxes", "Using Boxes.py"], "terms": {"you": [0, 2, 3, 4, 7, 9, 11, 12, 13, 14, 17, 19, 21], "ar": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 18, 19, 21], "think": [0, 14], "about": [0, 3, 4, 5, 6, 14, 16], "That": [0, 4], "": [0, 3, 6, 8, 13, 17], "great": [0, 21], "i": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 17, 18, 19, 21], "design": [0, 7], "re": [0, 21], "us": [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 13, 15, 16, 17, 18, 19], "extend": [0, 6, 8, 11, 13], "thi": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 18, 19, 21], "give": [0, 4, 13, 21], "some": [0, 2, 3, 5, 6, 11, 14, 15, 21], "guidelin": 0, "how": [0, 4, 6, 13, 18, 21], "your": [0, 1, 3, 9, 11, 12, 13, 14, 15, 19, 21], "most": [0, 2, 3, 4, 6, 7, 9, 11, 14, 17, 18, 21], "like": [0, 5, 6, 7, 8, 10, 11, 13, 14, 15, 18, 19, 21], "impact": 0, "develop": [0, 4, 5, 17, 19, 21], "chang": [0, 2, 4, 5, 9, 14, 17, 18, 19, 21], "merg": 0, "upstream": 0, "repositori": [0, 17, 18], "them": [0, 2, 3, 4, 5, 6, 11, 15, 21], "should": [0, 5, 6, 7, 9, 11, 13, 14, 18, 21], "just": [0, 2, 5, 6, 7, 14, 17, 21], "best": [0, 6], "practis": 0, "surpris": [0, 21], "don": [0, 5, 21], "t": [0, 1, 3, 5, 6, 7, 13, 21], "worri": 0, "find": [0, 11, 14, 21], "too": [0, 17], "complic": [0, 7], "It": [0, 2, 3, 4, 5, 6, 7, 10, 11, 17, 18, 21], "ok": 0, "leav": [0, 21], "final": 0, "touch": [0, 4, 6, 13], "someon": 0, "els": [0, 5, 13, 17], "often": [0, 2, 5, 6, 10], "compel": 0, "do": [0, 2, 4, 5, 6, 7, 10, 11, 13, 19, 21], "quick": 0, "thing": [0, 4, 7, 13, 15, 21], "solv": [0, 4], "immedi": 0, "need": [0, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 17, 18, 19, 21], "fine": [0, 1, 17], "But": [0, 2, 3, 4, 5, 6, 7, 10, 14, 17, 21], "nevertheless": [0, 4], "worth": 0, "right": [0, 2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 17, 21], "wai": [0, 2, 4, 5, 6, 17, 19, 21], "abl": [0, 21], "submit": 0, "For": [0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 17, 21], "one": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 21], "someth": [0, 2, 7, 18], "back": [0, 10, 13, 14, 18, 21], "commun": 0, "also": [0, 1, 2, 3, 6, 10, 13, 14, 17, 21], "pure": [0, 2, 17], "selfish": 0, "reason": [0, 4], "get": [0, 2, 3, 5, 6, 7, 8, 11, 13, 14, 17, 19, 21], "maintain": [0, 1], "make": [0, 2, 3, 4, 6, 7, 13, 14, 15, 19, 21], "properli": [0, 2, 6, 14, 15, 21], "easi": [0, 17], "here": [0, 4, 7], "easier": [0, 8, 9, 13, 21], "what": [0, 4, 5, 6, 7, 13], "up": [0, 1, 3, 7, 10, 11, 13, 15, 17, 21], "thei": [0, 1, 2, 4, 5, 6, 7, 10, 11, 14, 17, 21], "mai": [0, 5, 7, 8, 11, 12, 13, 14, 17, 19, 21], "appli": [0, 8, 11, 13], "vari": 0, "degre": [0, 5, 10, 13], "patch": [0, 7], "quit": 0, "readi": [0, 1], "yet": [0, 5, 6, 13, 15], "pleas": [0, 9, 19, 21], "state": [0, 21], "pull": [0, 6], "request": [0, 9, 13], "messag": 0, "statu": 0, "whether": [0, 4, 7, 11, 13, 21], "want": [0, 7, 17, 19, 21], "help": [0, 13, 17, 21], "go": [0, 2, 7, 10, 11, 14, 19], "finish": [0, 9, 10, 13], "own": [0, 1, 2, 3, 7, 11, 15, 18, 19, 21], "fork": [0, 9], "github": [0, 1, 13, 18, 19, 21], "befor": [0, 3, 9, 11, 13, 14, 21], "start": [0, 2, 4, 5, 6, 7, 8, 9, 10, 13, 14], "creat": [0, 2, 3, 5, 6, 8, 9, 11, 13, 15, 16, 17, 21], "separ": [0, 4, 5, 7, 8, 11, 13, 21], "branch": 0, "each": [0, 2, 6, 11, 13, 15, 21], "can": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 15, 17, 18, 19, 21], "master": [0, 19], "have": [0, 2, 3, 4, 5, 6, 7, 8, 11, 13, 14, 15, 17, 19, 21], "all": [0, 2, 4, 5, 6, 7, 8, 9, 10, 13, 14, 16, 17, 18, 21], "place": [0, 2, 4, 5, 6, 7, 10, 11, 13, 17, 21], "continu": [0, 4, 5, 7, 10, 15], "work": [0, 1, 2, 3, 4, 6, 7, 9, 15, 19, 21], "repeatedli": 0, "intend": [0, 11, 13, 15], "clean": 0, "self": [0, 4, 6, 9, 10, 18], "contain": [0, 8, 11, 13, 15], "error": [0, 13, 18, 21], "free": [0, 1], "order": [0, 7, 21], "squash": 0, "git": [0, 17, 18, 19], "rebas": 0, "The": [0, 1, 2, 3, 4, 6, 7, 9, 10, 13, 14, 15, 17, 21], "meaning": 0, "necessarili": 0, "reflect": 0, "wa": [0, 7], "current": [0, 1, 2, 4, 5, 6, 8, 10, 11, 13, 14, 17], "Be": [0, 3, 21], "prepar": [0, 9, 13], "rework": [0, 7], "being": [0, 8, 11, 13, 21], "base": [0, 7, 13, 15], "describ": [0, 21], "set": [0, 1, 2, 3, 4, 5, 7, 8, 9, 11, 13, 15, 17, 20], "intent": 0, "If": [0, 4, 7, 8, 11, 12, 13, 14, 19, 21], "discuss": 0, "idea": [0, 21], "open": [0, 9, 13, 14, 15, 19, 20, 21], "ticket": [0, 19, 21], "ask": [0, 16], "question": [0, 16], "encourag": [0, 21], "even": [0, 1, 6, 13, 14, 21], "know": [0, 11, 13], "There": [0, 2, 4, 5, 6, 11, 21], "mani": [0, 2, 3, 9, 11, 13, 21], "short": [0, 13, 21], "cut": [0, 1, 4, 6, 8, 13, 14, 15, 21], "point": [0, 1, 4, 5, 6, 10, 13], "direct": [0, 1, 2, 3, 4, 6, 10, 11, 13], "save": [0, 2, 4, 10, 13, 14, 21], "lot": [0, 7], "feed": 0, "feel": 0, "pr": 0, "progress": 0, "doe": [0, 2, 4, 5, 7, 10, 11, 13, 14, 17, 21], "follow": [0, 3, 7, 8, 10, 11, 18, 19, 21], "straight": [0, 4, 7, 11, 13, 21], "forward": [0, 7], "copi": [0, 2, 7, 12, 17], "anoth": [0, 4, 6, 7, 12, 13, 15, 19], "_templat": [0, 2, 9], "commit": 0, "librari": [0, 1, 2, 3, 7, 17, 21], "paramet": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 13, 17], "sane": [0, 21], "default": [0, 5, 6, 7, 8, 9, 10, 11, 13, 14], "instead": [0, 2, 11, 21], "hard": [0, 17], "dimens": [0, 8, 13, 21], "simpl": [0, 5, 6, 8, 13, 15, 21], "end": [0, 4, 5, 6, 8, 10, 13, 14, 15, 17], "singl": [0, 2, 3, 11, 15], "more": [0, 4, 5, 6, 7, 8, 13, 14, 16, 21], "multipl": [0, 3, 5, 6, 11, 13, 15, 21], "consid": 0, "thoroughli": 0, "depencendci": 0, "src": 0, "instal": [0, 16, 18, 19], "rst": 0, "file": [0, 14, 15, 17, 18, 21], "script": [0, 2, 17, 18, 19], "dockerfil": 0, "travi": 0, "yml": 0, "python": [0, 1, 2, 18, 19], "modul": [0, 16, 18, 20], "requir": [0, 2, 4, 10, 18, 19, 21], "txt": [0, 19], "setup": [0, 17, 18], "come": [0, 1, 4, 19, 21], "sphinx": 0, "larg": 0, "part": [0, 4, 5, 6, 7, 10, 12, 20, 21], "from": [0, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 15, 18, 19, 21], "doc": [0, 2], "string": [0, 2, 5, 6, 8, 11, 13], "ha": [0, 3, 4, 6, 10, 13, 17, 18, 21], "tendenc": 0, "outdat": 0, "encount": 0, "piec": [0, 1, 4, 6, 8, 11, 13, 15, 21], "out": [0, 4, 6, 7, 13, 14, 18, 21], "better": [0, 7, 13, 15, 19], "text": [0, 5, 13, 14, 17, 20, 21], "To": [0, 3, 4, 7, 9, 10, 11, 18, 21], "check": [0, 3, 5, 6, 7, 13, 14, 19, 21], "build": [0, 11, 17], "html": [0, 1, 3, 13, 14, 20], "compil": 0, "onlin": [0, 1], "updat": 0, "automat": [0, 1, 3, 4, 5, 9], "ci": 0, "soon": 0, "still": [0, 4, 5, 6, 7, 10, 14, 21], "without": [0, 1, 4, 8, 11, 13, 15, 17, 21], "an": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 17, 21], "exampl": [0, 6, 12], "item": [0, 11, 13, 15], "donat": 0, "good": [0, 7], "pictur": 0, "simpli": [0, 18], "attach": [0, 13, 15], "140": 0, "proper": [0, 2, 4, 7, 11], "sure": [0, 4, 14, 19], "sh": [0, 3, 13, 18, 21], "convert": [0, 2, 9, 13, 20, 21], "imagemagick": 0, "sed": 0, "sha256sum": 0, "jpg": 0, "name": [0, 2, 3, 9, 12, 13, 17, 18, 20], "case": [0, 4, 5, 6, 15, 18], "sensit": 0, "camelcas": 0, "1200": 0, "pixel": 0, "wide": [0, 17], "squar": [0, 13], "far": [0, 6, 13, 14], "3": [0, 1, 5, 6, 8, 13, 14, 17, 18, 19, 21], "4": [0, 6, 13, 21], "static": 0, "sampl": [0, 21], "show": [0, 15, 21], "bottom": [0, 5, 6, 7, 8, 13, 15, 20, 21], "page": [0, 14, 16], "when": [0, 4, 5, 6, 7, 11, 13, 15, 17, 18, 21], "boxesserv": [0, 2, 18, 19], "dir": 0, "execut": [0, 4, 17, 18, 19], "gen_thumbnail": 0, "thumbnail": [0, 9, 13, 20], "seen": 0, "main": [0, 2, 6, 9, 13, 18], "hover": 0, "over": [0, 2, 3, 4, 7, 9, 13, 15, 21], "entri": [0, 11, 13], "includ": [0, 1, 2, 7, 8, 11, 13, 14, 15], "generatornam": 0, "sha256": 0, "descript": [0, 2, 9, 13, 17, 20, 21], "much": [0, 6, 7, 13, 21], "than": [0, 4, 6, 9, 11, 15, 21], "its": [0, 2, 7, 13, 18, 19, 21], "argument": [0, 7, 12, 13], "deserv": 0, "hesit": 0, "small": [0, 4, 5, 21], "empti": 0, "space": [0, 5, 6, 8, 11, 13, 20, 21], "longer": [0, 21], "could": [0, 14], "hous": [0, 5, 15], "assembl": [0, 21], "instruct": [0, 19], "detail": [0, 3, 6, 9, 13, 14, 21], "interest": 0, "perfect": [0, 13], "we": [0, 13], "togeth": [0, 3, 6, 21], "serv": [0, 15], "websit": 0, "issu": [0, 4, 21], "inform": [0, 6], "necessari": [0, 4, 10, 17, 18], "reproduc": 0, "url": [0, 14], "broken": 0, "result": [0, 3, 4, 6, 13, 14, 21], "spot": 0, "suffici": [0, 21], "brief": 0, "otherwis": [0, 13, 21], "svg": [0, 1, 13, 14, 21], "screen": 0, "shot": 0, "add": [0, 3, 4, 6, 7, 9, 13, 19, 20, 21], "tag": [0, 15], "draw": [0, 4, 6, 7, 8, 9, 10, 11, 12, 13, 21], "addit": [0, 3, 4, 5, 6, 8, 9, 13, 21], "attent": 0, "ration": 0, "where": [0, 1, 11, 13, 15, 18, 21], "would": [0, 4, 13, 21], "try": 0, "precis": 0, "look": [0, 3, 6, 7, 11, 14, 17, 21], "which": [0, 2, 4, 6, 10, 11, 13, 15, 17, 18, 21], "import": [0, 2, 3, 4, 9, 18, 21], "less": [0, 6], "left": [0, 5, 8, 11, 13, 15, 21], "implement": [0, 2, 4, 5, 7, 9, 11, 13, 17, 21], "enhanc": 0, "gener": [1, 6, 7, 10, 11, 12, 16, 17, 20, 21], "http": [1, 13, 14, 18, 19], "www": [1, 13, 14, 19], "festi": 1, "info": 1, "index": [1, 11, 13, 16], "inkscap": [1, 2, 3, 13, 19], "plug": [1, 6], "write": [1, 7, 9, 12], "softwar": [1, 4, 14], "licens": [1, 14], "under": [1, 14, 19, 21], "gpl": [1, 14], "v3": 1, "written": [1, 3], "run": [1, 5, 13, 18, 19], "grow": [1, 5, 6, 13], "fulli": [1, 2, 15], "parametr": [1, 13, 15], "see": [1, 3, 9, 14, 17, 18, 19, 21], "florianfesti": [1, 18, 19], "io": 1, "full": [1, 4], "list": [1, 3, 8, 11, 13], "imag": [1, 9, 13, 20], "view": 1, "directli": [1, 3, 5, 9, 10], "web": [1, 2, 3, 18, 21], "browser": [1, 19], "postscript": [1, 13, 14, 21], "pstoedit": [1, 13, 17, 18], "extern": 1, "helper": [1, 11], "other": [1, 2, 3, 5, 6, 9, 10, 11, 13, 14, 15, 18, 19, 21], "vector": [1, 17, 20, 21], "format": [1, 9, 14, 17, 20], "dxf": [1, 13, 14, 17, 21], "plt": [1, 13, 17, 21], "aka": [1, 2, 4, 21], "hpgl": [1, 13], "gcode": [1, 13, 17, 21], "Of": 1, "cours": 1, "allow": [1, 2, 4, 5, 6, 7, 10, 11, 13, 15, 19, 21], "select": [1, 3, 13, 21], "thick": [1, 5, 6, 11, 13, 14], "materi": [1, 4, 6, 13, 14, 15, 21], "adjust": [1, 4, 12, 13, 15, 21], "length": [1, 3, 4, 5, 6, 8, 13, 20, 21], "width": [1, 5, 6, 8, 11, 13, 15, 20, 21], "join": [1, 6, 15], "finger": [1, 5, 7, 11, 13, 20], "element": 1, "burn": [1, 2, 5, 6, 8, 11, 12, 13, 14, 15], "compens": [1, 4, 8, 14], "remov": [1, 4, 7], "laser": [1, 4, 14, 15, 16, 21], "tune": [1, 21], "gap": [1, 6, 13, 21], "between": [1, 5, 6, 7, 13, 14, 21], "plywood": [1, 6, 21], "press": [1, 21], "fit": [1, 6, 8, 12, 13, 21], "ani": [1, 14], "glue": [1, 21], "joint": [1, 5, 7, 8, 11, 13], "hors": 1, "90": [1, 6, 7, 11, 13, 21], "edg": [1, 5, 7, 8, 9, 12, 15, 20], "connect": [1, 4, 5, 6, 7, 13, 19, 21], "Their": [1, 2], "size": [1, 5, 6, 8, 13, 15, 20, 21], "scale": [1, 6, 13, 21], "same": [1, 4, 6, 11, 13, 14, 15, 21], "appear": 1, "put": [1, 11, 13, 14], "hole": [1, 2, 4, 6, 7, 8, 11, 13, 17, 20, 21], "slot": [1, 5, 7, 13, 15, 20], "screw": [1, 7, 13, 15, 21], "bed": [1, 5, 13], "bolt": [1, 5, 13, 20], "although": [1, 2, 6, 7, 11], "support": [1, 2, 3, 6, 7, 11, 13, 15, 17, 21], "dovetail": [1, 6], "plane": [1, 13, 15], "flex": [1, 5, 8, 13, 15, 21], "bend": [1, 5, 13], "stretch": [1, 6, 13], "round": [1, 2, 5, 6, 8, 13, 14, 15, 21], "live": [1, 15], "hing": [1, 13, 15, 20, 21], "box": [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 17, 18, 19], "py": [2, 3, 4, 5, 6, 7, 9, 10, 17, 18, 19], "structur": 2, "sever": [2, 4, 6, 10, 15, 21], "distinct": 2, "tier": 2, "render": [2, 3, 4, 5, 8, 9, 13, 14, 20, 21], "differ": [2, 6, 7, 10, 13, 14, 15, 17, 21], "handl": [2, 3, 5, 6, 13, 20, 21], "readabl": 2, "form": [2, 6, 7, 11, 15], "locat": [2, 10, 17], "line": [2, 3, 4, 5, 6, 9, 10, 13, 14, 18, 19, 21], "boxes2inx": 2, "extens": [2, 3, 17], "boxes_exampl": 2, "ipynb": 2, "jupyt": 2, "notebook": 2, "A": [2, 4, 6, 7, 8, 11, 15, 18, 21], "sub": [2, 9, 13, 21], "class": [2, 3, 5, 7, 9, 13, 15, 17], "load": 2, "__init__": [2, 3, 7, 9, 18], "actual": [2, 6, 11, 13, 21], "found": [2, 18, 19], "ui": 2, "cli": [2, 18], "tool": [2, 3, 4, 7, 14, 15, 19, 21], "so": [2, 4, 5, 6, 7, 9, 13, 14, 18, 19, 21], "whenev": [2, 9], "either": [2, 5, 7, 8, 12, 13, 17], "exist": [2, 7, 9, 12], "sceleton": 2, "first": [2, 4, 7, 12, 14, 21], "call": [2, 3, 4, 5, 6, 8, 9, 10, 11, 13, 14, 18], "accord": [2, 4], "number": [2, 6, 8, 11, 13, 21], "standard": [2, 3, 21], "typic": [2, 4, 8, 9, 13, 21], "param": [2, 5, 7, 11, 13, 14, 21], "explain": 2, "api": [2, 3, 7, 16], "onli": [2, 5, 6, 7, 8, 10, 11, 13, 15, 17, 21], "real": [2, 21], "move": [2, 4, 5, 6, 7, 8, 10, 13, 20], "placement": 2, "middl": [2, 5, 6, 8, 13], "featur": [2, 4, 6, 19, 21], "posit": [2, 4, 5, 6, 10, 11, 13, 20], "coordin": [2, 5, 10, 11, 13], "system": [2, 5, 10, 13, 15, 17, 19], "absolut": [2, 6, 10, 13], "never": 2, "movement": [2, 10], "alwai": [2, 21], "rel": [2, 6, 10, 13], "few": [2, 6, 11, 14, 21], "function": [2, 3, 4, 6, 11, 13], "origin": [2, 10, 13], "conveni": [2, 3, 17], "return": [2, 10, 11, 13], "previous": 2, "been": [2, 17], "elev": [2, 13], "outset": [2, 6, 8, 13, 15, 20, 21], "pass": [2, 5, 7, 11], "associ": [2, 6], "char": [2, 6, 13, 20], "object": [2, 3, 5, 6, 7, 8, 9, 13, 15, 21], "itself": [2, 11, 13, 14], "corner": [2, 4, 5, 6, 8, 13, 15, 20, 21], "angl": [2, 4, 5, 6, 7, 8, 10, 13, 15, 21], "counter": [2, 4, 11, 13, 21], "clockwis": [2, 11, 13], "close": [2, 6, 9, 13, 15, 18, 20, 21], "while": [2, 4, 7, 17, 19], "neg": [2, 4, 13, 21], "protrus": [2, 21], "invers": [2, 13, 20], "drawn": [2, 4, 5, 6, 11, 13, 21], "correct": [2, 5, 11, 12, 13, 14, 21], "kerf": [2, 4, 13, 20, 21], "These": [2, 3, 5, 9, 14, 17, 21], "x": [2, 3, 4, 5, 6, 7, 8, 10, 11, 13, 15, 21], "y": [2, 3, 4, 5, 6, 7, 8, 10, 11, 13, 21], "somewher": [2, 5, 14, 21], "specif": [2, 21], "pattern": [2, 6, 10, 13, 15], "cairo": [2, 17], "now": [2, 5, 7, 8, 13, 18, 21], "encapsul": 2, "within": [2, 13, 17, 21], "method": [2, 3, 5, 6, 7, 9, 10, 11, 13, 17], "long": [2, 4], "term": 2, "goal": 2, "ctx": [2, 9, 10, 11], "context": [2, 10, 13], "made": [2, 5, 7, 9, 15, 21], "argpars": [3, 13], "command": [3, 4, 8, 9, 10, 12, 13, 17, 18, 19, 21], "code": [3, 6, 7, 9, 10, 17, 18], "interfac": [3, 9, 21], "limit": [3, 6], "kind": [3, 6, 8, 13], "type": [3, 5, 6, 7, 8, 11, 13, 15, 20], "int": [3, 13], "float": [3, 13], "str": [3, 13], "boolarg": [3, 13, 20], "altern": [3, 5, 6, 13, 17], "bool": 3, "argparsesect": [3, 13, 20], "e": [3, 6, 8, 10, 11, 13, 15, 19, 21], "g": [3, 6, 10, 11, 13, 15, 19, 21], "divid": [3, 15, 21], "argparseedgetyp": [3, 13, 20], "none": [3, 5, 6, 8, 9, 11, 13, 15], "sourc": [3, 5, 6, 7, 8, 9, 10, 11, 13, 15], "inx": [3, 13, 17, 20], "parser": [3, 13], "built": [3, 19], "after": [3, 4, 9, 10, 11, 13, 17, 21], "super": 3, "As": [3, 6, 14, 17], "common": 3, "ones": 3, "buildargpars": [3, 13, 20], "l": [3, 6, 11, 13, 15], "kw": [3, 6, 11, 13], "commonli": [3, 13], "new": [3, 7, 9, 12, 13, 21], "valu": [3, 4, 5, 6, 8, 9, 10, 11, 13, 14, 15, 21], "h": [3, 5, 6, 7, 8, 10, 11, 13, 21], "hi": [3, 7, 13], "sx": [3, 13, 21], "sy": [3, 9, 13, 18, 21], "bottom_edg": [3, 13], "top_edg": [3, 13], "outsid": [3, 4, 6, 11, 13, 14, 15], "nema_mount": [3, 13], "ad": [3, 4, 5, 7, 8, 13, 21], "normal": [3, 6, 7, 13, 20, 21], "argumentpars": [3, 13], "add_argu": 3, "dest": 3, "option_str": 3, "attribut": [3, 6, 7, 9, 13], "share": [3, 6, 11, 17], "group": [3, 15, 21], "classmethod": [3, 13], "parserargu": [3, 13, 20], "prefix": [3, 13], "possibl": [3, 11, 13], "regular": [3, 8, 13, 15, 17, 21], "instanc": [3, 5, 6, 8, 11, 13], "everywher": 3, "special": [3, 6, 7], "deal": [3, 4, 6], "constructor": 3, "content": [3, 16, 18, 20], "done": [3, 4, 7, 10, 11, 13], "parsearg": [3, 9, 13, 20], "peopl": 3, "won": [3, 21], "care": [3, 4], "frame": [3, 15], "overwrit": 3, "conflict": 3, "two": [4, 5, 6, 13, 15], "step": [4, 13, 18, 19, 20, 21], "mechan": [4, 15], "dure": 4, "post": 4, "process": [4, 21], "inner": [4, 6, 7, 8, 13, 14, 21], "bezier": 4, "loop": [4, 13, 21], "contin": 4, "motion": 4, "integr": 4, "low": [4, 5], "level": [4, 5, 7], "understand": 4, "catch": 4, "increas": 4, "radiu": [4, 5, 6, 8, 13, 21], "outer": [4, 8, 13, 15, 21], "outward": [4, 6, 13], "amount": [4, 6, 13, 21], "benefit": 4, "independ": [4, 5, 21], "adjac": [4, 8, 13], "aris": 4, "reduc": [4, 21], "radii": 4, "sharp": [4, 14, 21], "zero": [4, 21], "turn": [4, 6, 7], "flip": [4, 11, 13], "keep": [4, 5, 6, 9, 21], "unchang": 4, "lead": 4, "overcut": 4, "nice": 4, "dog": [4, 14], "bone": [4, 14], "might": [4, 5, 14, 18, 21], "dedic": 4, "cam": 4, "meant": [4, 21], "deem": 4, "accept": 4, "time": [4, 15], "clock": 4, "wise": 4, "propag": 4, "higher": 4, "ship": [4, 17, 18], "take": [4, 11, 13], "account": [4, 13, 21], "callback": [4, 7, 8, 13], "In": [4, 5, 6, 9, 14, 18, 21], "insid": [4, 6, 13, 21], "awar": [4, 21], "cc": [4, 11, 13, 20], "given": [4, 5, 8, 10, 11, 13, 15, 21], "note": [4, 7, 13, 14, 15, 18, 19, 21], "underneath": 4, "similar": [4, 6, 7, 8, 13, 15], "approach": [4, 17], "again": 4, "depend": [4, 7, 8, 13, 18, 21], "ist": 4, "vertic": [4, 7, 15], "horizont": [4, 7], "drawback": 4, "big": [4, 11], "cutter": [4, 14, 15, 16, 21], "cnc": 4, "mill": [4, 14, 21], "machin": [4, 15], "annoi": 4, "revers": [4, 5, 7, 13], "twice": [4, 7], "head": [4, 13, 21], "total": [4, 13, 21], "stop": 4, "path": [4, 13, 17, 19, 21], "scan": 4, "intersect": 4, "shorten": 4, "clear": 4, "former": [4, 7], "control": [4, 5, 13, 21], "err": 4, "side": [4, 5, 6, 7, 8, 11, 13, 15], "probabl": [4, 5, 6, 7, 9, 19], "further": 4, "optim": 4, "0": [5, 6, 8, 10, 11, 13, 14, 18, 21], "mm": [5, 13, 21], "curveto": [5, 13, 20], "x1": [5, 13], "y1": [5, 13], "x2": [5, 13], "y2": [5, 13], "x3": [5, 13], "y3": [5, 13], "1": [5, 6, 8, 13, 14, 18, 21], "2": [5, 6, 8, 13, 17, 18, 19, 21], "polylin": [5, 7, 13, 20], "arg": [5, 9, 13], "tupl": [5, 13], "bedbolthol": [5, 13, 20], "bedboltset": [5, 8, 13], "dimmens": [5, 13], "depth": [5, 6, 13, 21], "corrug": [5, 13], "gip": [5, 13], "area": [5, 6, 13], "groov": [5, 6, 13], "true": [5, 6, 8, 11, 13], "fals": [5, 6, 8, 11, 13, 15], "fix": [5, 6, 13], "door": [5, 13, 15], "awai": [5, 6, 9, 13, 14, 21], "hl": [5, 13], "r": [5, 6, 8, 10, 13, 19], "30": [5, 13, 21], "height": [5, 6, 7, 8, 11, 13, 15, 20, 21], "th": [5, 13], "interupt": 5, "border": [5, 6, 8, 13, 21], "enabl": 5, "rectangularwal": [5, 7, 8, 10, 13, 20], "8": [5, 13], "hold": [5, 6, 13, 15], "becaus": [5, 7, 18], "concern": 5, "complet": [5, 7, 14], "those": [5, 11, 21], "both": [5, 6, 10, 13, 15, 21], "segment": [5, 6, 8, 13, 15], "som": 5, "sort": 5, "artifact": 5, "outlin": [5, 13], "d": [5, 6, 8, 11, 13], "postion": [5, 13], "rectangularhol": [5, 13, 20], "dx": [5, 13], "dy": [5, 13], "center_x": [5, 13], "center_i": [5, 13], "rectangular": [5, 6, 8, 13, 15], "center": [5, 8, 13], "dhole": [5, 13, 20], "w": [5, 13], "rel_w": [5, 13], "75": [5, 13], "shaft": [5, 13, 21], "flat": [5, 13, 15, 18], "shape": [5, 6, 13, 15, 18, 19, 21], "overrid": [5, 13], "diamet": [5, 6, 8, 13, 20, 21], "measur": [5, 6, 13, 14], "against": [5, 13, 21], "percent": [5, 13], "orent": [5, 13], "rotat": [5, 11, 13], "flathol": [5, 13, 20], "oppos": [5, 6, 13, 21], "orient": [5, 13], "align": [5, 13], "fontsiz": [5, 13], "10": [5, 13, 15, 19], "color": [5, 20], "font": [5, 13], "arial": [5, 13], "combin": [5, 7, 11, 13], "top": [5, 6, 7, 8, 13, 15, 18, 20], "nema": [5, 7, 13, 15, 20], "screwhol": [5, 13], "mount": [5, 7, 15, 20, 21], "stepper": [5, 13], "motor": [5, 7, 13, 15], "nomin": [5, 13, 21], "tenth": [5, 13], "inch": [5, 13], "tx": [5, 13, 20], "star": [5, 13, 15], "100": [5, 13], "flex2d": [5, 13, 20], "fill": [5, 13, 15, 17, 21], "rectangl": [5, 6, 13, 14, 21], "axi": [5, 7, 11, 13], "nuthol": [5, 13, 20], "avail": [5, 6, 8, 13, 17, 18], "fingerhol": [5, 6, 8, 13, 20], "match": [5, 6, 8, 13], "access": [5, 6, 7, 13, 19], "fingerholesat": [5, 6, 7, 11], "ventil": 5, "roundedpl": [5, 8, 13, 20], "futur": [5, 21], "global": [5, 9, 17], "hexholesset": [5, 13, 20], "dist": [5, 13, 20], "style": [5, 6, 13, 15, 21], "5": [5, 6, 13, 21], "circl": [5, 13, 20], "replac": [5, 7, 8, 13, 21], "hexholesrectangl": [5, 13, 20], "skip": [5, 13], "hex": [5, 8, 13], "b": [5, 13], "present": [5, 13, 21], "posx": [5, 13], "posi": [5, 13], "hexholescircl": [5, 13, 20], "hexholespl": [5, 13, 20], "rc": [5, 13], "plate": [5, 8, 13, 15], "hexholeshex": [5, 13, 20], "turtl": [6, 8, 10, 13], "graphic": [6, 10, 21], "mean": [6, 14, 21], "suppos": [6, 9], "ensur": 6, "assum": [6, 13, 17], "symetr": [6, 13], "unsur": [6, 7], "except": 6, "mainli": 6, "provid": [6, 17, 21], "callabl": [6, 11, 13], "bit": [6, 7, 17, 19, 21], "surround": [6, 8, 13, 21], "virtual": 6, "elsewher": 6, "f": [6, 8, 11, 13], "virual": 6, "cutout": [6, 21], "offset": [6, 13], "kept": 6, "dict": [6, 11], "kei": [6, 11, 13], "aabb": 6, "reserv": 6, "c": [6, 13], "clickconnector": [6, 13, 20], "clickedg": [6, 13, 20], "dovetailjoint": [6, 13, 20], "dovetailjointcounterpart": [6, 13, 20], "outsetedg": [6, 13, 20], "fingerjointedg": [6, 13, 20], "fingerjointedgecounterpart": [6, 13, 20], "grippingedg": [6, 13, 20], "mountingedg": [6, 13, 20], "fingerholeedg": [6, 13, 20], "ijk": 6, "lidholeedg": [6, 13, 20], "lidedg": [6, 13, 20], "m": [6, 13, 19], "lidsideleft": [6, 13, 20], "lidleft": [6, 13, 20], "n": [6, 8, 11, 13], "lidsideright": [6, 13, 20], "lidright": [6, 13, 20], "oo": 6, "chesthing": [6, 13, 20], "pp": 6, "chesthingetop": [6, 13, 20], "q": [6, 13], "chesthingefront": [6, 13, 20], "chesthingepin": [6, 13, 20], "rackedg": [6, 13, 20], "stackableedg": [6, 13, 20], "stackableedgetop": [6, 13, 20], "\u0161": [6, 13], "stackablefeet": [6, 13, 20], "stackableholeedgetop": [6, 13, 20], "roundedtrianglefingerholesedg": [6, 13, 20], "roundedtriangleedg": [6, 13, 20], "uuvv": [6, 13], "cabinethingeedg": [6, 13, 20], "flexedg": [6, 13, 20], "handleedg": [6, 13, 20], "handleholeedg": [6, 13, 20], "z": [6, 13], "groovededgecounterpart": [6, 13, 20], "groovededg": [6, 13, 20], "baseedg": [6, 13, 20], "abstract": [6, 13], "endangl": [6, 13, 20], "Not": [6, 13], "margin": [6, 13, 20], "startangl": [6, 13, 20], "startwidth": [6, 13, 20], "begin": [6, 13], "below": [6, 11, 13, 17, 21], "__call__": 6, "store": [6, 13, 15, 19], "overload": [6, 13], "absolute_param": [6, 13, 20], "relative_param": [6, 13, 20], "suport": [6, 13], "via": [6, 13, 18, 19], "checkvalu": [6, 13, 20], "rang": [6, 13], "rais": [6, 13], "valueerror": [6, 13], "edgeobject": [6, 13, 20], "sequenc": [6, 13], "setvalu": [6, 13, 20], "gripset": [6, 13, 20], "wave": [6, 8, 13, 20], "bump": [6, 13, 20], "fingerjointset": [6, 13, 20], "feet": [6, 13, 21], "recess": [6, 13], "stackableset": [6, 13, 20], "60": [6, 13], "holedist": [6, 13, 21], "distanc": [6, 7, 8, 13, 21], "ss\u0161\u0161": [6, 13], "fingerset": [6, 13], "sheet": [6, 21], "third": 6, "stabl": 6, "especi": [6, 21], "wall": [6, 7, 8, 13, 14, 15, 21], "parallel": [6, 13], "crossingfingerholeedg": [6, 13, 20], "abov": [6, 10, 11, 13, 14, 19, 21], "surroundingspac": [6, 13, 21], "meet": [6, 8, 13], "edge_width": [6, 8, 13], "plai": [6, 13, 15], "extra": [6, 13], "extra_length": [6, 13, 21], "grind": [6, 13, 21], "mark": [6, 13, 21], "ffh": [6, 13], "boltpolici": [6, 13, 20], "distribut": [6, 13, 14], "evenli": [6, 13], "flatli": 6, "bigger": [6, 14, 21], "stronger": 6, "bare": 6, "forc": [6, 15, 18, 21], "dovetailset": [6, 13, 20], "50": [6, 13, 21], "widen": [6, 13], "80": [6, 13], "stick": [6, 13, 21], "four": [6, 13], "dd": [6, 13], "flexset": [6, 13, 20], "05": [6, 13], "hint": [6, 13], "shortend": [6, 13], "perpendicular": [6, 13], "slid": [6, 13], "pice": [6, 13], "through": [6, 13], "slottededg": [6, 13, 20], "section": [6, 7, 13], "compos": [6, 13], "hingeset": [6, 13, 20], "flush": [6, 9, 13, 20], "lid": [6, 15, 20], "overlap": [6, 13], "pinwidth": [6, 13], "lower": [6, 13], "disk": [6, 9, 13, 15], "pin": [6, 13], "grip_percentag": [6, 13], "percentag": [6, 13], "hingestrength": [6, 13], "arc": [6, 13, 21], "axl": [6, 13], "grip_length": [6, 13], "he": [6, 13], "layout": [6, 13, 15], "decid": 7, "scratch": [7, 9, 12], "least": [7, 14], "basic": [7, 21], "later": [7, 8, 13], "appropri": 7, "electronicsbox": 7, "closedbox": 7, "basi": 7, "port": [7, 18], "board": 7, "spacer": 7, "pbc": 7, "non": [7, 17, 21], "braket": [7, 15], "produc": [7, 14], "cube": 7, "variabl": [7, 19], "shelf": 7, "slant": [7, 15], "floor": [7, 15], "pretti": 7, "calcul": [7, 17, 21], "tricki": 7, "debug": [7, 13], "got": 7, "attempt": 7, "yourself": 7, "front": [7, 13, 15, 20], "fingerjoint": 7, "trai": [7, 21], "variant": [7, 15, 18], "slope": [7, 15], "typetrai": 7, "alreadi": 7, "purpos": 7, "triangl": [7, 8, 13, 15], "binfrontedg": 7, "intern": [7, 21], "lazi": [7, 17], "bin": [7, 13, 17, 18], "switch": [7, 15], "trafficlight": 7, "techniqu": 7, "bass": [7, 15], "record": [7, 15], "endpin": [7, 15], "monopod": 7, "repeat": [7, 21], "realli": 7, "everyth": 7, "pain": 7, "hand": [7, 13, 21], "symmetr": 7, "mirror": [7, 11, 13], "beyond": 7, "openscad": 7, "tradit": 7, "cad": 7, "program": 7, "coupl": [8, 9], "whole": 8, "eeee": [8, 13], "ignore_width": [8, 13], "holesmargin": [8, 13], "holesset": [8, 13], "bedbolt": [8, 13], "label": [8, 11, 13], "identifi": [8, 13], "ment": [8, 13], "etch": [8, 13, 20, 21], "flangedwal": [8, 13, 20], "ffff": [8, 13], "flang": [8, 13], "rectangulartriangl": [8, 13, 20], "eee": [8, 13], "num": [8, 13], "triangular": [8, 13], "diagon": [8, 13], "toward": [8, 13], "hypothenus": [8, 13], "regularpolygonwal": [8, 13, 20], "polygon": [8, 13, 15], "sector": [8, 13], "central": [8, 13], "polygonwal": [8, 13, 20], "multi": [8, 13], "arrai": [8, 13], "wrap": [8, 13], "suppport": [8, 13], "wallpiec": [8, 13], "extend_corn": [8, 13], "surroundingwal": [8, 11, 13, 20], "count": [8, 13], "fite": [8, 13], "around": [8, 13, 21], "separet": 8, "disc": [8, 13, 20], "defaultvalu": [8, 13], "waivyknob": [8, 13, 20], "20": [8, 13], "45": [8, 13], "waivi": [8, 13], "grip": [8, 13, 15, 20], "knob": [8, 13, 15], "maximum": [8, 13], "concaveknob": [8, 13, 20], "70": [8, 13, 21], "dent": [8, 13], "proport": [8, 13, 21], "circumferen": [8, 13], "remain": [8, 13, 21], "circumfer": [8, 13], "ringseg": [8, 13, 20], "r_outsid": [8, 13], "r_insid": [8, 13], "ring": [8, 13], "anlg": [8, 13], "span": [8, 13], "canva": [9, 10, 13], "variou": [9, 15], "oper": [9, 17], "user": [9, 17, 18, 19, 21], "interact": 9, "pars": [9, 13], "argv": [9, 13, 18], "output": [9, 13, 18], "packag": [9, 17, 18, 20], "uigroup": [9, 13, 20], "titl": [9, 13], "properti": [9, 13, 14], "getallboxgener": [9, 13, 20], "getallgeneratormodul": [9, 13, 20], "uniqu": 9, "next": [10, 13, 15], "hide": [10, 21], "last": [10, 18, 21], "restor": [10, 11, 13, 20], "ignor": 10, "consist": 10, "moveto": [10, 13, 20], "movearc": [10, 13, 20], "row": [10, 11, 13, 15, 21], "manag": 10, "saved_context": [10, 13, 20], "primit": 10, "disadvantag": 10, "discourag": 10, "matchiung": 10, "reset": 10, "heal": 10, "doubt": 10, "bound": 11, "expect": [11, 18], "holder": [11, 15], "shorter": 11, "handi": 11, "lambda": 11, "express": 11, "block": [11, 19], "getentri": [11, 13, 20], "idx": [11, 13], "word": 11, "option": [11, 13, 15, 18, 21], "down": [11, 13, 15], "along": [11, 13], "ommit": [11, 13], "iter": 11, "charact": 11, "desir": [11, 17], "per": [11, 13, 17], "By": [11, 14], "dove": [11, 13, 15], "tail": [11, 13, 15], "regist": 11, "dictionari": 11, "denomin": 11, "partmatrix": 11, "keyword": [11, 13], "treat": 11, "architectur": 12, "navig": 12, "annot": [13, 20], "black": [13, 20, 21], "blue": [13, 20, 21], "cyan": [13, 20], "etching_deep": [13, 20], "green": [13, 20, 21], "inner_cut": [13, 20], "magenta": [13, 20], "outer_cut": [13, 20], "red": [13, 20, 21], "white": [13, 20], "yellow": [13, 20], "endwidth": [13, 20], "drawbolt": [13, 20], "po": 13, "numfing": [13, 20], "smaller": [13, 14, 21], "aim": 13, "cabinet": [13, 15], "u": 13, "cabinethingeset": [13, 20], "bore": 13, "eyes_per_hing": 13, "ey": [13, 21], "minimum": 13, "o": 13, "chest": 13, "chesthingeset": [13, 20], "pin_height": 13, "hinge_strength": 13, "finger_joints_on_box": 13, "finger_joints_on_lid": 13, "ooppqq": 13, "pinheight": [13, 20], "p": [13, 21], "click": 13, "hook": [13, 20], "hookoffset": [13, 20], "hookwidth": [13, 20], "clickset": [13, 20], "bottom_radiu": 13, "compoundedg": [13, 20], "compound": 13, "orthogon": 13, "fingerjointbas": [13, 20], "calcfing": [13, 20], "fingerlength": [13, 20], "draw_fing": [13, 20], "firsthalf": 13, "spring": 13, "barb": 13, "snap": [13, 21], "gearset": [13, 20], "rack": [13, 15], "pinion": 13, "modulu": 13, "pressur": 13, "profile_shift": 13, "profil": [13, 15], "shift": 13, "clearanc": 13, "groovededgebas": [13, 20], "groove_arc": [13, 20], "inv": 13, "groove_soft_arc": [13, 20], "groove_triangl": [13, 20], "is_invers": [13, 20], "groovedset": [13, 20], "tri_angl": 13, "arc_angl": 13, "120": [13, 21], "fraction": 13, "invert": [13, 21], "interleav": 13, "param_arc": [13, 20], "param_flat": [13, 20], "param_softarc": [13, 20], "softarc": 13, "param_triangl": [13, 20], "zz": 13, "bumpout": 13, "drawer": [13, 15, 21], "extra_height": [13, 20], "handleedgeset": [13, 20], "hole_width": 13, "40": 13, "hole_height": 13, "on_sid": 13, "panel": [13, 15], "yy": 13, "_revers": 13, "flushlen": [13, 20], "outsetlen": [13, 20], "hingepin": [13, 20], "iijjkk": 13, "slide": [13, 15], "rightsid": [13, 20], "lidset": [13, 20], "determin": 13, "second_pin": 13, "lock": 13, "pear": [13, 21], "mountingset": [13, 20], "valid": 13, "configur": [13, 15, 21], "sens": 13, "integ": 13, "125": 13, "d_shaft": 13, "d_head": 13, "6": [13, 21], "param_back": [13, 20], "param_ext": [13, 20], "param_front": [13, 20], "param_in": [13, 20], "param_left": [13, 20], "param_right": [13, 20], "param_tab": [13, 20], "tab": 13, "roundedtriangleedgeset": [13, 20], "150": 13, "r_hole": 13, "stackablebaseedg": [13, 20], "stackabl": [13, 15], "getdescript": [13, 20], "filenam": 13, "fmt": 13, "metadata": 13, "ai": [13, 21], "ps2ai": 13, "lbrn2": 13, "pdf": [13, 21], "plot": 13, "svg_ponoko": 13, "getformat": [13, 20], "getsurfac": [13, 20], "http_header": [13, 20], "vnd": 13, "plain": 13, "charset": 13, "utf": 13, "applic": [13, 18], "hp": 13, "xml": 13, "pstoedit_candid": [13, 20], "usr": [13, 17, 18], "ex": 13, "calc_circular_pitch": [13, 20], "math": 13, "circular": 13, "pitch": 13, "drawpoint": [13, 20], "kerfdir": 13, "gearcarri": [13, 20], "spoke_width": 13, "mount_radiu": 13, "mount_hol": 13, "generate_spok": [13, 20], "root_radiu": 13, "spoke": 13, "unit_factor": 13, "unit_label": 13, "constraint": 13, "li": 13, "teeth": [13, 20], "defin": [13, 14, 21], "room": 13, "collis": 13, "optionpars": [13, 20], "prog": 13, "usag": 13, "epilog": 13, "parent": 13, "formatter_class": 13, "helpformatt": 13, "prefix_char": 13, "fromfile_prefix_char": 13, "argument_default": 13, "conflict_handl": 13, "add_help": 13, "allow_abbrev": 13, "add_opt": [13, 20], "long_": 13, "inkbool": [13, 20], "gear_calcul": [13, 20], "num_teeth": 13, "circular_pitch": 13, "pressure_angl": 13, "ring_gear": 13, "calc": 13, "spur": 13, "undercut": 13, "generate_rack_point": [13, 20], "tooth_count": 13, "addendum": 13, "base_height": 13, "tab_length": 13, "draw_guid": 13, "suitabl": 13, "involut": 13, "infinit": 13, "linear": 13, "ramp": 13, "mesh": 13, "highest": 13, "lowest": 13, "downward": 13, "tooth": 13, "exactli": [13, 21], "generate_spur_point": [13, 20], "base_radiu": 13, "pitch_radiu": 13, "outer_radiu": 13, "accuracy_involut": 13, "accuracy_circular": 13, "core": 13, "have_undercut": [13, 20], "pitch_angl": 13, "k": 13, "specifi": 13, "caus": [13, 14], "val": 13, "involute_intersect_angl": [13, 20], "rb": 13, "linspac": [13, 20], "interp": 13, "ll": 13, "must": [13, 21], "point_on_circl": [13, 20], "xy": 13, "coord": 13, "undercut_max_k": [13, 20], "comput": [13, 15], "occur": [13, 18], "undercut_min_angl": [13, 20], "undercut_min_teeth": [13, 20], "deg": 13, "metric_modul": 13, "upward": 13, "safeti": 13, "min_teeth": 13, "ceil": 13, "18": 13, "17": 13, "arconcircl": [13, 20], "spanning_angl": 13, "outgoing_angl": 13, "belt": [13, 15], "droftart": 13, "januari": 13, "2012": 13, "thingivers": 13, "com": [13, 18, 19], "11256": 13, "me": 13, "prusajr": 13, "prusamendel": 13, "josef": 13, "prusa": 13, "3104": 13, "gilesbathg": 13, "2079": 13, "nophead": 13, "data": [13, 14], "oem": 13, "cadregist": 13, "asp": 13, "ppow_entri": 13, "compani": 13, "915217": 13, "elementid": 13, "07807803": 13, "metric": 13, "ureth": 13, "wv0025": 13, "model": 13, "sdp": 13, "si": 13, "d265": 13, "d265t016": 13, "getprofil": [13, 20], "profile_data": [13, 20], "40dp": 13, "457": 13, "226": 13, "at5": 13, "19": [13, 15], "268": 13, "gt2_2mm": 13, "764": 13, "494": 13, "gt2_3mm": 13, "169": 13, "31": 13, "gt2_5mm": 13, "969": 13, "952": 13, "905": 13, "359": 13, "htd_3mm": 13, "289": 13, "27": 13, "htd_5mm": 13, "199": 13, "781": 13, "htd_8mm": 13, "607": 13, "603": 13, "mxl": 13, "508": 13, "321": 13, "t10": 13, "13": 13, "t2_5": 13, "7": [13, 17, 18, 19], "678": 13, "t5": 13, "264": 13, "xl": 13, "051": 13, "07264": 13, "1778": 13, "6523": 13, "591": 13, "064": 13, "254": 13, "381": 13, "5715": 13, "9": [13, 21], "525": 13, "6858": 13, "032": 13, "93": 13, "7467": 13, "796": 13, "026": 13, "08": 13, "612775": 13, "574719": 13, "010187": 13, "546453": 13, "0381": 13, "355953": 13, "3683": 13, "327604": 13, "405408": 13, "291086": 13, "433388": 13, "248548": 13, "451049": 13, "202142": 13, "4572": 13, "202494": 13, "248653": 13, "291042": 13, "327609": 13, "356306": 13, "546806": 13, "574499": 13, "134129": 13, "058023": 13, "005488": 13, "984595": 13, "021547": 13, "914806": 13, "047569": 13, "849614": 13, "082947": 13, "789978": 13, "127073": 13, "736857": 13, "179338": 13, "691211": 13, "239136": 13, "653999": 13, "305859": 13, "349199": 13, "959203": 13, "286933": 13, "054635": 13, "201914": 13, "127346": 13, "099961": 13, "173664": 13, "986896": 13, "18992": 13, "986543": 13, "099614": 13, "201605": 13, "286729": 13, "653646": 13, "690859": 13, "73651": 13, "789644": 13, "849305": 13, "914539": 13, "984392": 13, "057906": 13, "747183": 13, "647876": 13, "037218": 13, "598311": 13, "130528": 13, "578556": 13, "238423": 13, "547158": 13, "343077": 13, "504649": 13, "443762": 13, "451556": 13, "53975": 13, "358229": 13, "636924": 13, "2484": 13, "707276": 13, "127259": 13, "750044": 13, "76447": 13, "504797": 13, "547291": 13, "578605": 13, "648009": 13, "155171": 13, "065317": 13, "016448": 13, "989057": 13, "062001": 13, "93297": 13, "130969": 13, "90364": 13, "217664": 13, "863705": 13, "408181": 13, "800056": 13, "591388": 13, "713587": 13, "765004": 13, "60519": 13, "926747": 13, "469751": 13, "032548": 13, "320719": 13, "108119": 13, "162625": 13, "153462": 13, "168577": 13, "932921": 13, "988924": 13, "065168": 13, "975908": 13, "797959": 13, "03212": 13, "646634": 13, "121224": 13, "534534": 13, "256431": 13, "474258": 13, "426861": 13, "446911": 13, "570808": 13, "411774": 13, "712722": 13, "368964": 13, "852287": 13, "318597": 13, "989189": 13, "260788": 13, "123115": 13, "195654": 13, "25375": 13, "12331": 13, "380781": 13, "043869": 13, "503892": 13, "935264": 13, "612278": 13, "817959": 13, "706414": 13, "693181": 13, "786237": 13, "562151": 13, "851687": 13, "426095": 13, "9027": 13, "286235": 13, "939214": 13, "143795": 13, "961168": 13, "9685": 13, "143796": 13, "935263": 13, "123207": 13, "195509": 13, "26065": 13, "318507": 13, "368956": 13, "411872": 13, "447132": 13, "474611": 13, "534583": 13, "646678": 13, "121223": 13, "798064": 13, "6797": 13, "600907": 13, "006138": 13, "525342": 13, "024024": 13, "45412": 13, "052881": 13, "388351": 13, "091909": 13, "329145": 13, "140328": 13, "277614": 13, "197358": 13, "234875": 13, "262205": 13, "202032": 13, "334091": 13, "75224": 13, "57093": 13, "719538": 13, "642815": 13, "676883": 13, "707663": 13, "62542": 13, "764693": 13, "566256": 13, "813112": 13, "500512": 13, "85214": 13, "4293": 13, "880997": 13, "353742": 13, "898883": 13, "274949": 13, "905021": 13, "275281": 13, "354056": 13, "429576": 13, "500731": 13, "566411": 13, "625508": 13, "676919": 13, "719531": 13, "752233": 13, "20273": 13, "235433": 13, "278045": 13, "329455": 13, "388553": 13, "454233": 13, "525384": 13, "600904": 13, "135062": 13, "048323": 13, "015484": 13, "974284": 13, "058517": 13, "919162": 13, "123974": 13, "889176": 13, "206728": 13, "81721": 13, "579614": 13, "800806": 13, "653232": 13, "778384": 13, "72416": 13, "750244": 13, "792137": 13, "716685": 13, "856903": 13, "678005": 13, "918199": 13, "634505": 13, "975764": 13, "586483": 13, "029338": 13, "534238": 13, "078662": 13, "47807": 13, "123476": 13, "418278": 13, "16352": 13, "355162": 13, "198533": 13, "289019": 13, "228257": 13, "22015": 13, "25243": 13, "148854": 13, "270793": 13, "07543": 13, "283087": 13, "000176": 13, "28905": 13, "075081": 13, "283145": 13, "148515": 13, "270895": 13, "219827": 13, "252561": 13, "288716": 13, "228406": 13, "354879": 13, "19869": 13, "418018": 13, "163675": 13, "477831": 13, "123623": 13, "534017": 13, "078795": 13, "586276": 13, "029452": 13, "634307": 13, "975857": 13, "677809": 13, "91827": 13, "716481": 13, "856953": 13, "750022": 13, "792167": 13, "778133": 13, "724174": 13, "800511": 13, "653236": 13, "816857": 13, "888471": 13, "919014": 13, "974328": 13, "048362": 13, "89036": 13, "741168": 13, "02669": 13, "61387": 13, "100806": 13, "518984": 13, "21342": 13, "467026": 13, "3556": 13, "427162": 13, "960967": 13, "398568": 13, "089602": 13, "359437": 13, "213531": 13, "310296": 13, "332296": 13, "251672": 13, "445441": 13, "184092": 13, "552509": 13, "108081": 13, "653042": 13, "024167": 13, "746585": 13, "932877": 13, "832681": 13, "834736": 13, "910872": 13, "730271": 13, "980701": 13, "62001": 13, "041713": 13, "504478": 13, "09345": 13, "384202": 13, "135455": 13, "259708": 13, "167271": 13, "131524": 13, "188443": 13, "198511": 13, "131296": 13, "188504": 13, "259588": 13, "167387": 13, "384174": 13, "135616": 13, "504527": 13, "093648": 13, "620123": 13, "04194": 13, "730433": 13, "980949": 13, "834934": 13, "911132": 13, "933097": 13, "832945": 13, "024398": 13, "746846": 13, "108311": 13, "653291": 13, "184308": 13, "552736": 13, "251865": 13, "445639": 13, "310455": 13, "332457": 13, "359552": 13, "213647": 13, "39863": 13, "089664": 13, "301471": 13, "16611": 13, "012093": 13, "038062": 13, "047068": 13, "919646": 13, "10297": 13, "813182": 13, "177844": 13, "720989": 13, "269734": 13, "645387": 13, "376684": 13, "588694": 13, "496739": 13, "553229": 13, "627944": 13, "460801": 13, "470025": 13, "411413": 13, "691917": 13, "343887": 13, "905691": 13, "259126": 13, "110563": 13, "158035": 13, "30575": 13, "041518": 13, "490467": 13, "910478": 13, "66393": 13, "76582": 13, "825356": 13, "608446": 13, "973961": 13, "439261": 13, "10896": 13, "259169": 13, "22957": 13, "069074": 13, "335006": 13, "869878": 13, "424485": 13, "662487": 13, "497224": 13, "447804": 13, "552437": 13, "226732": 13, "589341": 13, "607153": 13, "226511": 13, "589461": 13, "447712": 13, "552654": 13, "66252": 13, "497516": 13, "870027": 13, "424833": 13, "069329": 13, "33539": 13, "259517": 13, "229973": 13, "439687": 13, "109367": 13, "608931": 13, "974358": 13, "766344": 13, "825731": 13, "911018": 13, "664271": 13, "042047": 13, "490765": 13, "158526": 13, "305998": 13, "259547": 13, "110755": 13, "344204": 13, "905821": 13, "411591": 13, "691983": 13, "588592": 13, "645238": 13, "720834": 13, "81305": 13, "919553": 13, "038012": 13, "166095": 13, "660421": 13, "621898": 13, "006033": 13, "587714": 13, "023037": 13, "560056": 13, "049424": 13, "541182": 13, "083609": 13, "417357": 13, "424392": 13, "398413": 13, "458752": 13, "370649": 13, "48514": 13, "336324": 13, "502074": 13, "297744": 13, "508035": 13, "336268": 13, "370452": 13, "39811": 13, "416983": 13, "540808": 13, "559752": 13, "587516": 13, "621841": 13, "06511": 13, "971998": 13, "007239": 13, "882718": 13, "028344": 13, "79859": 13, "062396": 13, "720931": 13, "108479": 13, "651061": 13, "165675": 13, "590298": 13, "233065": 13, "539962": 13, "309732": 13, "501371": 13, "394759": 13, "879071": 13, "105025": 13, "840363": 13, "190052": 13, "789939": 13, "266719": 13, "729114": 13, "334109": 13, "659202": 13, "391304": 13, "581518": 13, "437387": 13, "497376": 13, "47144": 13, "408092": 13, "492545": 13, "314979": 13, "499784": 13, "408091": 13, "497371": 13, "581499": 13, "659158": 13, "729028": 13, "789791": 13, "840127": 13, "878718": 13, "501018": 13, "539726": 13, "59015": 13, "650975": 13, "720887": 13, "798571": 13, "882713": 13, "971997": 13, "839258": 13, "770246": 13, "021652": 13, "726369": 13, "079022": 13, "529167": 13, "620889": 13, "485025": 13, "67826": 13, "416278": 13, "699911": 13, "484849": 13, "528814": 13, "770114": 13, "632126": 13, "568549": 13, "004939": 13, "507539": 13, "019367": 13, "450023": 13, "042686": 13, "396912": 13, "074224": 13, "349125": 13, "113379": 13, "307581": 13, "159508": 13, "273186": 13, "211991": 13, "246868": 13, "270192": 13, "009802": 13, "920362": 13, "983414": 13, "978433": 13, "949018": 13, "030788": 13, "907524": 13, "076798": 13, "859829": 13, "115847": 13, "80682": 13, "147314": 13, "749402": 13, "170562": 13, "688471": 13, "184956": 13, "624921": 13, "189895": 13, "624971": 13, "688622": 13, "749607": 13, "807043": 13, "860055": 13, "907754": 13, "949269": 13, "9837": 13, "010193": 13, "246907": 13, "273295": 13, "307726": 13, "349276": 13, "397039": 13, "450111": 13, "507589": 13, "568563": 13, "525411": 13, "41777": 13, "015495": 13, "320712": 13, "059664": 13, "239661": 13, "129034": 13, "180042": 13, "220133": 13, "793044": 13, "050219": 13, "733574": 13, "141021": 13, "652507": 13, "210425": 13, "555366": 13, "254759": 13, "447675": 13, "270353": 13, "239711": 13, "320844": 13, "417919": 13, "mirrorx": [13, 20], "tooth_spaceing_curvefit": [13, 20], "tooth_spac": [13, 20], "tooth_pitch": 13, "pitch_line_offset": 13, "robotarg": [13, 20], "includenon": 13, "choic": [13, 20], "translat": [13, 21], "robotarmmm": [13, 20], "servo2": 13, "_robotarm": 13, "arm": [13, 15], "robotarmmu": [13, 20], "knuckl": 13, "robotarmuu": [13, 20], "eyeedg": [13, 20], "driven": 13, "servo9g": [13, 20], "axle_po": [13, 20], "22": 13, "hinge_depth": [13, 20], "hinge_width": [13, 20], "28": 13, "servo_axl": [13, 20], "12": 13, "servo9gt": [13, 20], "35": 13, "servoarg": [13, 20], "buildedg": [13, 20], "mmnn": 13, "getsizeinmm": [13, 20], "tree": 13, "getviewbox": [13, 20], "svgmerg": [13, 18, 20], "tickspermm": [13, 20], "circlepoint": [13, 20], "dotproduct": [13, 20], "v1": 13, "v2": 13, "dot": 13, "product": 13, "mmul": [13, 20], "m0": 13, "m1": 13, "v": 13, "lenght": 13, "rotm": [13, 20], "matrix": 13, "tangent": [13, 20], "radui": 13, "vadd": [13, 20], "sum": [13, 21], "vclip": [13, 20], "vdiff": [13, 20], "p1": 13, "p2": 13, "point1": 13, "point2": 13, "vlength": [13, 20], "vorthogon": [13, 20], "vscalmul": [13, 20], "vtransl": [13, 20], "viewnam": 13, "j": 13, "_": 13, "addpart": [13, 20], "__name__": 13, "addsettingsarg": [13, 20], "adjusts": [13, 20], "e1": 13, "e2": 13, "edgecorn": [13, 20], "edge1": 13, "edge2": 13, "fillhol": [13, 20], "max_radiu": 13, "hspace": 13, "bspace": 13, "min_radiu": 13, "bar_length": 13, "max_random": 13, "1000": 13, "random": 13, "hbar": 13, "vbar": 13, "x0": 13, "y0": 13, "hexagon": 13, "octagon": 13, "bar": [13, 15], "fingerholerectangl": [13, 20], "meassur": 13, "latch": [13, 20], "mirrori": [13, 20], "mountinghol": [13, 20], "nema_s": [13, 20], "16": 13, "15": 13, "11": 13, "23": 13, "14": 13, "26": 13, "39": 13, "42": 13, "56": 13, "38": 13, "47": [13, 18], "24": 13, "36": 13, "49": 13, "34": 13, "86": 13, "73": [13, 18], "69": 13, "110": 13, "55": 13, "89": 13, "partsmatrix": [13, 20], "regularpolygon": [13, 20], "regularpolygonat": [13, 20], "regularpolygonhol": [13, 20], "corner_radiu": 13, "set_font": [13, 20], "bold": 13, "ital": 13, "serif": 13, "san": 13, "monospac": 13, "set_source_color": [13, 20], "pen": 13, "showborderpoli": [13, 20], "prependicular": 13, "surroundingwallpiec": [13, 20], "cbnr": 13, "geometri": 13, "correspond": 13, "boolean": 13, "trapezoidsidewal": [13, 20], "h0": 13, "h1": 13, "trapezoid": 13, "upper": 13, "trapezoidwal": [13, 20], "tx_size": [13, 20], "61": 13, "82": 13, "96": 13, "06": 13, "87": 13, "85": 13, "25": [13, 21], "64": [13, 19], "ui_group": [13, 20], "misc": [13, 21], "webinterfac": [13, 20], "nut": 13, "m10": 13, "m12": 13, "m14": 13, "21": 13, "m16": 13, "m2": 13, "m20": 13, "m24": 13, "m3": 13, "m30": 13, "46": 13, "m36": 13, "m4": 13, "m42": 13, "65": 13, "m48": 13, "m5": 13, "m56": 13, "m6": 13, "m64": 13, "95": 13, "51": 13, "m8": 13, "delta": 13, "delat": 13, "fillholesset": [13, 20], "fill_pattern": 13, "hole_styl": 13, "hole_max_radiu": 13, "hole_min_radiu": 13, "space_between_hol": 13, "space_to_bord": 13, "holecol": [13, 20], "func": 13, "wrapper": 13, "coordiant": 13, "ye": 14, "gplv3": 14, "gnu": 14, "org": [14, 19], "grant": 14, "reach": 14, "oblig": 14, "fall": [14, 21], "well": 14, "bug": 14, "calip": [14, 15], "hundredth": 14, "milimet": 14, "loos": [14, 21], "light": [14, 15], "heavi": 14, "pressfit": 14, "choosen": 14, "looser": [14, 21], "tighter": [14, 21], "offer": [14, 17, 21], "plausabl": 14, "hit": 14, "button": [14, 21], "unfortun": 14, "unit": [14, 15], "categori": 14, "refer": 14, "chain": 14, "inner_corn": 14, "meta": 14, "document": [14, 17], "viewer": 14, "window": [14, 17], "editor": 14, "luck": 14, "organ": 15, "layer": 15, "twist": 15, "storag": 15, "card": 15, "servic": 15, "hatch": 15, "integrad": 15, "dice": 15, "transpar": 15, "acryl": [15, 21], "3u": 15, "rail": 15, "half": [15, 21], "bookend": 15, "hang": 15, "clamp": 15, "jig": 15, "stack": [15, 21], "paper": 15, "coaster": 15, "etc": [15, 21], "19inch": 15, "music": 15, "equip": 15, "two_piec": 15, "slip": 15, "enclosur": 15, "heart": 15, "roll": 15, "shutter": 15, "stype": 15, "drill": 15, "insert": [15, 21], "input": 15, "webarg": 15, "pile": 15, "paintbox": 15, "hobbi": 15, "paint": 15, "microrack": 15, "rackabl": 15, "sbc": 15, "pi": 15, "spice": 15, "honei": 15, "comb": 15, "wine": 15, "din": 15, "electr": 15, "junction": 15, "wallcaliperhold": 15, "chisel": 15, "stuff": 15, "compart": [15, 21], "plier": 15, "platform": 15, "driver": 15, "wrench": 15, "fan": 15, "test": [15, 21], "serrat": 15, "2d": 15, "ident": [15, 19], "stage": 15, "beam": 15, "holdfast": 15, "tabl": 15, "possibli": 15, "servo": [15, 20], "power": 15, "robot": [15, 20], "agricola": 15, "revis": 15, "edit": [15, 17, 18], "game": 15, "expans": 15, "desktop": 15, "split": 15, "atreu": 15, "keyboard": 15, "bottl": 15, "fridg": 15, "can_storag": 15, "coffeecapsuleshold": 15, "coffe": 15, "capsul": 15, "showcas": 15, "coin": 15, "diplai": 15, "flyer": 15, "leaflet": 15, "book": 15, "cover": 15, "spine": 15, "wit": 15, "mouth": 15, "larger": [15, 21], "laptop": 15, "knife": [15, 21], "magazinefil": 15, "magazin": 15, "bench": 15, "suppli": [15, 21], "maktia": 15, "18v": 15, "batteri": 15, "otto": 15, "lc": 15, "chassi": 15, "dii": 15, "bodi": 15, "leg": 15, "foam": 15, "sole": 15, "bot": 15, "smartphon": 15, "desk": 15, "pole": 15, "royal": 15, "ur": 15, "traffic": 15, "led": 15, "lamp": 15, "bird": 15, "glide": 15, "tower": 15, "pipe": 15, "pipecalc": 15, "balanc": 15, "solid": 15, "engrav": 15, "cylindr": 15, "silverwarebox": 15, "cuttleri": 15, "stand": 15, "carri": 15, "frequent": 16, "contribut": 16, "search": 16, "project": 17, "cmd": 17, "binari": 17, "python3": [17, 18, 19], "life": 17, "drop": 17, "local": [17, 18], "anyth": 17, "due": 17, "checkout": 17, "supposs": 17, "clone": [17, 18, 19], "manual": [17, 19], "plugin": [17, 19], "unix": 17, "boxes2inkscap": 17, "taget": 17, "userspac": 17, "config": [17, 18], "On": 17, "target": 17, "directori": [17, 18], "prefer": 17, "inkex": 17, "symlink": 17, "maco": 17, "nativ": 17, "subsystem": 17, "linux": 17, "recommend": 18, "homebrew": 18, "brew": 18, "cairio": 18, "pkg": 18, "pip3": 18, "markdown": [18, 19], "affin": [18, 19], "download": [18, 19], "server": [18, 19], "8000": [18, 19], "cask": 18, "xquartz": 18, "root": 18, "traceback": 18, "recent": 18, "martin": 18, "107": 18, "run_gener": 18, "lib": 18, "site": 18, "py3": 18, "egg": 18, "594": 18, "svgutil": [18, 20], "inkscapefil": 18, "144": 18, "lxml": 18, "etre": 18, "et": 18, "importerror": 18, "dlopen": 18, "app": [18, 19], "resourc": 18, "python2": 18, "symbol": 18, "_pybasestring_typ": 18, "referenc": 18, "namespac": 18, "version": [18, 19], "miss": [18, 21], "workaround": 18, "At": 18, "79": 18, "export": 18, "pythonpath": 18, "python_v": 18, "break": 18, "restart": 18, "troubl": 19, "known": [19, 21], "x86": 19, "pip": 19, "forgot": 19, "environ": 19, "zip": 19, "archiv": 19, "extract": 19, "folder": 19, "cd": 19, "usernam": 19, "downloadsbox": 19, "scriptsboxesserv": 19, "notifi": 19, "firewal": 19, "network": 19, "address": 19, "localhost": 19, "fun": 19, "addition": 19, "scriptsbox": 19, "wsl": 19, "newer": 19, "onc": [19, 21], "ubuntu": 19, "microsoft": 19, "enter": 19, "subpackag": 20, "submodul": 20, "gear": 20, "pullei": 20, "visibl": 21, "shelv": 21, "unstabl": 21, "millimet": 21, "plan": 21, "second": 21, "retain": 21, "technic": 21, "littl": 21, "veri": 21, "everi": 21, "unless": 21, "reliabl": 21, "ply": 21, "wood": 21, "100th": 21, "notabl": 21, "stiff": 21, "harder": 21, "brittl": 21, "picki": 21, "perimet": 21, "spongi": 21, "overs": 21, "unders": 21, "intuit": 21, "01": 21, "005mm": 21, "choos": 21, "burntest": 21, "ps2edit": 21, "easili": 21, "bridg": 21, "theori": 21, "affect": 21, "practic": 21, "3mm": 21, "exact": 21, "With": 21, "cannot": 21, "untouch": 21, "backarc": 21, "naiv": 21, "construct": 21, "belong": 21, "correctli": 21, "prone": 21, "weird": 21, "world": 21, "clue": 21, "arbitrari": 21, "column": 21, "stagger": 21, "compar": 21, "colon": 21, "numberofsect": 21, "equal": 21, "overallwidth": 21, "freeli": 21, "mix": 21, "disabl": 21, "creation": 21, "taken": 21, "accommod": 21, "respons": 21, "subtract": 21, "simplest": 21, "fold": 21, "off": 21, "butted": 21, "sacrif": 21, "stabil": 21, "convent": 21, "comment": 21, "hopefulli": 21, "obviou": 21}, "objects": {"": [[5, 0, 1, "", "NutHole"], [13, 2, 0, "-", "boxes"]], "argparse.ArgumentParser": [[3, 1, 1, "", "add_argument"]], "boxes": [[13, 0, 1, "", "ArgparseEdgeType"], [13, 0, 1, "", "BoolArg"], [13, 0, 1, "", "Boxes"], [13, 2, 0, "-", "Color"], [13, 0, 1, "", "HexHolesSettings"], [13, 0, 1, "", "NutHole"], [13, 4, 1, "", "argparseSections"], [13, 4, 1, "", "dist"], [13, 2, 0, "-", "edges"], [13, 0, 1, "", "fillHolesSettings"], [13, 2, 0, "-", "formats"], [13, 2, 0, "-", "gears"], [13, 2, 0, "-", "generators"], [13, 4, 1, "", "holeCol"], [13, 2, 0, "-", "lids"], [13, 2, 0, "-", "parts"], [13, 2, 0, "-", "pulley"], [13, 4, 1, "", "restore"], [13, 2, 0, "-", "robot"], [13, 2, 0, "-", "servos"], [13, 2, 0, "-", "svgutil"], [13, 2, 0, "-", "vectors"]], "boxes.ArgparseEdgeType": [[13, 3, 1, "", "edges"], [13, 1, 1, "", "html"], [13, 1, 1, "", "inx"], [13, 3, 1, "", "names"]], "boxes.BoolArg": [[13, 1, 1, "", "html"]], "boxes.Boxes": [[13, 1, 1, "", "NEMA"], [13, 1, 1, "", "TX"], [9, 1, 1, "", "__init__"], [13, 1, 1, "", "addPart"], [13, 1, 1, "", "addParts"], [13, 1, 1, "", "addSettingsArgs"], [13, 1, 1, "", "adjustSize"], [13, 1, 1, "", "bedBoltHole"], [13, 1, 1, "", "buildArgParser"], [13, 1, 1, "", "cc"], [13, 1, 1, "", "circle"], [13, 1, 1, "", "close"], [13, 1, 1, "", "corner"], [13, 1, 1, "", "curveTo"], [13, 1, 1, "", "dHole"], [13, 3, 1, "", "description"], [13, 1, 1, "", "edge"], [13, 1, 1, "", "edgeCorner"], [13, 1, 1, "", "fillHoles"], [13, 1, 1, "", "fingerHoleRectangle"], [13, 1, 1, "", "flangedWall"], [13, 1, 1, "", "flatHole"], [13, 1, 1, "", "flex2D"], [13, 1, 1, "", "getEntry"], [13, 1, 1, "", "grip"], [13, 1, 1, "", "handle"], [13, 1, 1, "", "hexHolesCircle"], [13, 1, 1, "", "hexHolesHex"], [13, 1, 1, "", "hexHolesPlate"], [13, 1, 1, "", "hexHolesRectangle"], [13, 1, 1, "", "hole"], [13, 1, 1, "", "latch"], [13, 1, 1, "", "mirrorX"], [13, 1, 1, "", "mirrorY"], [13, 1, 1, "", "mountingHole"], [13, 1, 1, "", "move"], [13, 1, 1, "", "moveArc"], [13, 1, 1, "", "moveTo"], [13, 3, 1, "", "nema_sizes"], [13, 1, 1, "", "open"], [13, 1, 1, "", "parseArgs"], [13, 1, 1, "", "partsMatrix"], [13, 1, 1, "", "polygonWall"], [13, 1, 1, "", "polygonWalls"], [13, 1, 1, "", "polyline"], [13, 1, 1, "", "rectangularHole"], [13, 1, 1, "", "rectangularTriangle"], [13, 1, 1, "", "rectangularWall"], [13, 1, 1, "", "regularPolygon"], [13, 1, 1, "", "regularPolygonAt"], [13, 1, 1, "", "regularPolygonHole"], [13, 1, 1, "", "regularPolygonWall"], [13, 1, 1, "", "render"], [13, 1, 1, "", "roundedPlate"], [13, 1, 1, "", "saved_context"], [13, 1, 1, "", "set_font"], [13, 1, 1, "", "set_source_color"], [13, 1, 1, "", "showBorderPoly"], [13, 1, 1, "", "step"], [13, 1, 1, "", "surroundingWall"], [13, 1, 1, "", "surroundingWallPiece"], [13, 1, 1, "", "text"], [13, 1, 1, "", "trapezoidSideWall"], [13, 1, 1, "", "trapezoidWall"], [13, 3, 1, "", "tx_sizes"], [13, 3, 1, "", "ui_group"], [13, 3, 1, "", "webinterface"]], "boxes.Color": [[13, 0, 1, "", "Color"]], "boxes.Color.Color": [[13, 3, 1, "", "ANNOTATIONS"], [13, 3, 1, "", "BLACK"], [13, 3, 1, "", "BLUE"], [13, 3, 1, "", "CYAN"], [13, 3, 1, "", "ETCHING"], [13, 3, 1, "", "ETCHING_DEEP"], [13, 3, 1, "", "GREEN"], [13, 3, 1, "", "INNER_CUT"], [13, 3, 1, "", "MAGENTA"], [13, 3, 1, "", "OUTER_CUT"], [13, 3, 1, "", "RED"], [13, 3, 1, "", "WHITE"], [13, 3, 1, "", "YELLOW"]], "boxes.HexHolesSettings": [[13, 3, 1, "", "absolute_params"], [13, 3, 1, "", "relative_params"]], "boxes.NutHole": [[13, 3, 1, "", "sizes"]], "boxes.edges": [[13, 0, 1, "", "BaseEdge"], [13, 0, 1, "", "BoltPolicy"], [13, 0, 1, "", "Bolts"], [13, 0, 1, "", "CabinetHingeEdge"], [13, 0, 1, "", "CabinetHingeSettings"], [13, 0, 1, "", "ChestHinge"], [13, 0, 1, "", "ChestHingeFront"], [13, 0, 1, "", "ChestHingePin"], [13, 0, 1, "", "ChestHingeSettings"], [13, 0, 1, "", "ChestHingeTop"], [13, 0, 1, "", "ClickConnector"], [13, 0, 1, "", "ClickEdge"], [13, 0, 1, "", "ClickSettings"], [13, 0, 1, "", "CompoundEdge"], [13, 0, 1, "", "CrossingFingerHoleEdge"], [13, 0, 1, "", "DoveTailJoint"], [13, 0, 1, "", "DoveTailJointCounterPart"], [13, 0, 1, "", "DoveTailSettings"], [13, 0, 1, "", "Edge"], [13, 0, 1, "", "FingerHoleEdge"], [13, 0, 1, "", "FingerHoles"], [13, 0, 1, "", "FingerJointBase"], [13, 0, 1, "", "FingerJointEdge"], [13, 0, 1, "", "FingerJointEdgeCounterPart"], [13, 0, 1, "", "FingerJointSettings"], [13, 0, 1, "", "FlexEdge"], [13, 0, 1, "", "FlexSettings"], [13, 0, 1, "", "GearSettings"], [13, 0, 1, "", "GripSettings"], [13, 0, 1, "", "GrippingEdge"], [13, 0, 1, "", "GroovedEdge"], [13, 0, 1, "", "GroovedEdgeBase"], [13, 0, 1, "", "GroovedEdgeCounterPart"], [13, 0, 1, "", "GroovedSettings"], [13, 0, 1, "", "HandleEdge"], [13, 0, 1, "", "HandleEdgeSettings"], [13, 0, 1, "", "HandleHoleEdge"], [13, 0, 1, "", "Hinge"], [13, 0, 1, "", "HingePin"], [13, 0, 1, "", "HingeSettings"], [13, 0, 1, "", "LidEdge"], [13, 0, 1, "", "LidHoleEdge"], [13, 0, 1, "", "LidLeft"], [13, 0, 1, "", "LidRight"], [13, 0, 1, "", "LidSettings"], [13, 0, 1, "", "LidSideLeft"], [13, 0, 1, "", "LidSideRight"], [13, 0, 1, "", "MountingEdge"], [13, 0, 1, "", "MountingSettings"], [13, 0, 1, "", "OutSetEdge"], [13, 0, 1, "", "RackEdge"], [13, 0, 1, "", "RoundedTriangleEdge"], [13, 0, 1, "", "RoundedTriangleEdgeSettings"], [13, 0, 1, "", "RoundedTriangleFingerHolesEdge"], [13, 0, 1, "", "Settings"], [13, 0, 1, "", "Slot"], [13, 0, 1, "", "SlottedEdge"], [13, 0, 1, "", "StackableBaseEdge"], [13, 0, 1, "", "StackableEdge"], [13, 0, 1, "", "StackableEdgeTop"], [13, 0, 1, "", "StackableFeet"], [13, 0, 1, "", "StackableHoleEdgeTop"], [13, 0, 1, "", "StackableSettings"], [13, 4, 1, "", "argparseSections"], [13, 4, 1, "", "getDescriptions"]], "boxes.edges.BaseEdge": [[6, 1, 1, "", "__call__"], [13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 1, 1, "", "endAngle"], [13, 1, 1, "", "endwidth"], [13, 1, 1, "", "margin"], [13, 1, 1, "", "spacing"], [13, 1, 1, "", "startAngle"], [13, 1, 1, "", "startwidth"]], "boxes.edges.BoltPolicy": [[13, 1, 1, "", "drawbolt"], [13, 1, 1, "", "numFingers"]], "boxes.edges.Bolts": [[13, 1, 1, "", "drawBolt"], [13, 1, 1, "", "numFingers"]], "boxes.edges.CabinetHingeEdge": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 1, 1, "", "parts"], [13, 1, 1, "", "startwidth"]], "boxes.edges.CabinetHingeSettings": [[13, 3, 1, "", "absolute_params"], [13, 1, 1, "", "edgeObjects"], [13, 3, 1, "", "relative_params"]], "boxes.edges.ChestHinge": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 1, 1, "", "endwidth"], [13, 1, 1, "", "margin"], [13, 1, 1, "", "startwidth"]], "boxes.edges.ChestHingeFront": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 1, 1, "", "startwidth"]], "boxes.edges.ChestHingePin": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 1, 1, "", "margin"]], "boxes.edges.ChestHingeSettings": [[13, 3, 1, "", "absolute_params"], [13, 1, 1, "", "checkValues"], [13, 1, 1, "", "edgeObjects"], [13, 1, 1, "", "pinheight"], [13, 3, 1, "", "relative_params"]], "boxes.edges.ChestHingeTop": [[13, 3, 1, "", "char"], [13, 1, 1, "", "endwidth"], [13, 1, 1, "", "margin"], [13, 1, 1, "", "startwidth"]], "boxes.edges.ClickConnector": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 1, 1, "", "finger"], [13, 1, 1, "", "hook"], [13, 1, 1, "", "hookOffset"], [13, 1, 1, "", "hookWidth"], [13, 1, 1, "", "margin"]], "boxes.edges.ClickEdge": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 1, 1, "", "margin"], [13, 1, 1, "", "startwidth"]], "boxes.edges.ClickSettings": [[13, 3, 1, "", "absolute_params"], [13, 1, 1, "", "edgeObjects"], [13, 3, 1, "", "relative_params"]], "boxes.edges.CompoundEdge": [[13, 3, 1, "", "description"], [13, 1, 1, "", "endwidth"], [13, 1, 1, "", "margin"], [13, 1, 1, "", "startwidth"]], "boxes.edges.CrossingFingerHoleEdge": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"]], "boxes.edges.DoveTailJoint": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 1, 1, "", "margin"], [13, 3, 1, "", "positive"]], "boxes.edges.DoveTailJointCounterPart": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 1, 1, "", "margin"], [13, 3, 1, "", "positive"]], "boxes.edges.DoveTailSettings": [[13, 3, 1, "", "absolute_params"], [13, 1, 1, "", "edgeObjects"], [13, 3, 1, "", "relative_params"]], "boxes.edges.Edge": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 3, 1, "", "positive"]], "boxes.edges.FingerHoleEdge": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 1, 1, "", "startwidth"]], "boxes.edges.FingerJointBase": [[13, 1, 1, "", "calcFingers"], [13, 1, 1, "", "fingerLength"]], "boxes.edges.FingerJointEdge": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 1, 1, "", "draw_finger"], [13, 1, 1, "", "margin"], [13, 3, 1, "", "positive"], [13, 1, 1, "", "startwidth"]], "boxes.edges.FingerJointEdgeCounterPart": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 3, 1, "", "positive"]], "boxes.edges.FingerJointSettings": [[13, 3, 1, "", "absolute_params"], [13, 1, 1, "", "checkValues"], [13, 1, 1, "", "edgeObjects"], [13, 3, 1, "", "relative_params"]], "boxes.edges.FlexEdge": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"]], "boxes.edges.FlexSettings": [[13, 3, 1, "", "absolute_params"], [13, 1, 1, "", "checkValues"], [13, 3, 1, "", "relative_params"]], "boxes.edges.GearSettings": [[13, 3, 1, "", "absolute_params"], [13, 3, 1, "", "relative_params"]], "boxes.edges.GripSettings": [[13, 3, 1, "", "absolute_params"], [13, 1, 1, "", "edgeObjects"], [13, 3, 1, "", "relative_params"]], "boxes.edges.GrippingEdge": [[13, 1, 1, "", "bumps"], [13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 1, 1, "", "margin"], [13, 1, 1, "", "wave"]], "boxes.edges.GroovedEdge": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 3, 1, "", "inverse"]], "boxes.edges.GroovedEdgeBase": [[13, 1, 1, "", "groove_arc"], [13, 1, 1, "", "groove_soft_arc"], [13, 1, 1, "", "groove_triangle"], [13, 1, 1, "", "is_inverse"]], "boxes.edges.GroovedEdgeCounterPart": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 3, 1, "", "inverse"]], "boxes.edges.GroovedSettings": [[13, 3, 1, "", "PARAM_ARC"], [13, 3, 1, "", "PARAM_FLAT"], [13, 3, 1, "", "PARAM_SOFTARC"], [13, 3, 1, "", "PARAM_TRIANGLE"], [13, 3, 1, "", "absolute_params"], [13, 1, 1, "", "edgeObjects"]], "boxes.edges.HandleEdge": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 3, 1, "", "extra_height"], [13, 1, 1, "", "margin"]], "boxes.edges.HandleEdgeSettings": [[13, 3, 1, "", "absolute_params"], [13, 1, 1, "", "edgeObjects"], [13, 3, 1, "", "relative_params"]], "boxes.edges.HandleHoleEdge": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 3, 1, "", "extra_height"], [13, 1, 1, "", "margin"]], "boxes.edges.Hinge": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 1, 1, "", "flush"], [13, 1, 1, "", "flushlen"], [13, 1, 1, "", "margin"], [13, 1, 1, "", "outset"], [13, 1, 1, "", "outsetlen"]], "boxes.edges.HingePin": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 1, 1, "", "endwidth"], [13, 1, 1, "", "flush"], [13, 1, 1, "", "flushlen"], [13, 1, 1, "", "margin"], [13, 1, 1, "", "outset"], [13, 1, 1, "", "outsetlen"], [13, 1, 1, "", "startwidth"]], "boxes.edges.HingeSettings": [[13, 3, 1, "", "absolute_params"], [13, 1, 1, "", "checkValues"], [13, 1, 1, "", "edgeObjects"], [13, 3, 1, "", "relative_params"]], "boxes.edges.LidEdge": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"]], "boxes.edges.LidHoleEdge": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"]], "boxes.edges.LidLeft": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 3, 1, "", "rightside"]], "boxes.edges.LidRight": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 1, 1, "", "endwidth"], [13, 1, 1, "", "margin"], [13, 3, 1, "", "rightside"], [13, 1, 1, "", "startwidth"]], "boxes.edges.LidSettings": [[13, 3, 1, "", "absolute_params"], [13, 1, 1, "", "edgeObjects"], [13, 3, 1, "", "relative_params"]], "boxes.edges.LidSideLeft": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 3, 1, "", "rightside"]], "boxes.edges.LidSideRight": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 1, 1, "", "endwidth"], [13, 1, 1, "", "margin"], [13, 3, 1, "", "rightside"], [13, 1, 1, "", "startwidth"]], "boxes.edges.MountingEdge": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 1, 1, "", "margin"], [13, 1, 1, "", "startwidth"]], "boxes.edges.MountingSettings": [[13, 3, 1, "", "PARAM_BACK"], [13, 3, 1, "", "PARAM_EXT"], [13, 3, 1, "", "PARAM_FRONT"], [13, 3, 1, "", "PARAM_IN"], [13, 3, 1, "", "PARAM_LEFT"], [13, 3, 1, "", "PARAM_RIGHT"], [13, 3, 1, "", "PARAM_TAB"], [13, 3, 1, "", "absolute_params"], [13, 1, 1, "", "edgeObjects"]], "boxes.edges.OutSetEdge": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 3, 1, "", "positive"], [13, 1, 1, "", "startwidth"]], "boxes.edges.RackEdge": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 1, 1, "", "margin"]], "boxes.edges.RoundedTriangleEdge": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 1, 1, "", "margin"]], "boxes.edges.RoundedTriangleEdgeSettings": [[13, 3, 1, "", "absolute_params"], [13, 1, 1, "", "edgeObjects"], [13, 3, 1, "", "relative_params"]], "boxes.edges.RoundedTriangleFingerHolesEdge": [[13, 3, 1, "", "char"], [13, 1, 1, "", "startwidth"]], "boxes.edges.Settings": [[13, 3, 1, "", "absolute_params"], [13, 1, 1, "", "checkValues"], [13, 1, 1, "", "edgeObjects"], [13, 1, 1, "", "parserArguments"], [13, 3, 1, "", "relative_params"], [13, 1, 1, "", "setValues"]], "boxes.edges.Slot": [[13, 3, 1, "", "description"]], "boxes.edges.SlottedEdge": [[13, 3, 1, "", "description"], [13, 1, 1, "", "endwidth"], [13, 1, 1, "", "margin"], [13, 1, 1, "", "startwidth"]], "boxes.edges.StackableBaseEdge": [[13, 3, 1, "", "bottom"], [13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 1, 1, "", "margin"], [13, 1, 1, "", "startwidth"]], "boxes.edges.StackableEdge": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"]], "boxes.edges.StackableEdgeTop": [[13, 3, 1, "", "bottom"], [13, 3, 1, "", "char"], [13, 3, 1, "", "description"]], "boxes.edges.StackableFeet": [[13, 3, 1, "", "char"], [13, 3, 1, "", "description"]], "boxes.edges.StackableHoleEdgeTop": [[13, 3, 1, "", "bottom"], [13, 3, 1, "", "char"], [13, 3, 1, "", "description"], [13, 1, 1, "", "startwidth"]], "boxes.edges.StackableSettings": [[13, 3, 1, "", "absolute_params"], [13, 1, 1, "", "checkValues"], [13, 1, 1, "", "edgeObjects"], [13, 3, 1, "", "relative_params"]], "boxes.fillHolesSettings": [[13, 3, 1, "", "absolute_params"]], "boxes.formats": [[13, 0, 1, "", "Formats"]], "boxes.formats.Formats": [[13, 1, 1, "", "convert"], [13, 3, 1, "", "formats"], [13, 1, 1, "", "getFormats"], [13, 1, 1, "", "getSurface"], [13, 3, 1, "", "http_headers"], [13, 3, 1, "", "pstoedit_candidates"]], "boxes.gears": [[13, 0, 1, "", "Gears"], [13, 0, 1, "", "OptionParser"], [13, 4, 1, "", "gear_calculations"], [13, 4, 1, "", "generate_rack_points"], [13, 4, 1, "", "generate_spur_points"], [13, 4, 1, "", "have_undercut"], [13, 4, 1, "", "inkbool"], [13, 4, 1, "", "involute_intersect_angle"], [13, 4, 1, "", "linspace"], [13, 4, 1, "", "point_on_circle"], [13, 4, 1, "", "undercut_max_k"], [13, 4, 1, "", "undercut_min_angle"], [13, 4, 1, "", "undercut_min_teeth"]], "boxes.gears.Gears": [[13, 1, 1, "", "calc_circular_pitch"], [13, 1, 1, "", "drawPoints"], [13, 1, 1, "", "gearCarrier"], [13, 1, 1, "", "generate_spokes"], [13, 1, 1, "", "sizes"]], "boxes.gears.OptionParser": [[13, 1, 1, "", "add_option"], [13, 3, 1, "", "types"]], "boxes.generators": [[13, 0, 1, "", "UIGroup"], [13, 4, 1, "", "getAllBoxGenerators"], [13, 4, 1, "", "getAllGeneratorModules"]], "boxes.generators.UIGroup": [[13, 1, 1, "", "add"], [13, 5, 1, "", "image"], [13, 5, 1, "", "thumbnail"]], "boxes.generators._template.BOX": [[3, 1, 1, "id0", "__init__"]], "boxes.generators.abox": [[15, 0, 1, "", "ABox"]], "boxes.generators.agricolainsert": [[15, 0, 1, "", "AgricolaInsert"]], "boxes.generators.alledges": [[15, 0, 1, "", "AllEdges"]], "boxes.generators.angledbox": [[15, 0, 1, "", "AngledBox"]], "boxes.generators.angledcutjig": [[15, 0, 1, "", "AngledCutJig"]], "boxes.generators.arcade": [[15, 0, 1, "", "Arcade"]], "boxes.generators.atreus21": [[15, 0, 1, "", "Atreus21"]], "boxes.generators.basedbox": [[15, 0, 1, "", "BasedBox"]], "boxes.generators.bayonetbox": [[15, 0, 1, "", "BayonetBox"]], "boxes.generators.bintray": [[7, 0, 1, "", "BinFrontEdge"], [15, 0, 1, "", "BinTray"]], "boxes.generators.birdhouse": [[15, 0, 1, "", "BirdHouse"]], "boxes.generators.bottlestack": [[15, 0, 1, "", "BottleStack"]], "boxes.generators.bottletag": [[15, 0, 1, "", "BottleTag"]], "boxes.generators.breadbox": [[15, 0, 1, "", "BreadBox"]], "boxes.generators.burntest": [[15, 0, 1, "", "BurnTest"]], "boxes.generators.can_storage": [[15, 0, 1, "", "CanStorage"]], "boxes.generators.cardbox": [[15, 0, 1, "", "CardBox"]], "boxes.generators.cardholder": [[15, 0, 1, "", "CardHolder"]], "boxes.generators.castle": [[15, 0, 1, "", "Castle"]], "boxes.generators.closedbox": [[15, 0, 1, "", "ClosedBox"]], "boxes.generators.coffeecapsulesholder": [[15, 0, 1, "", "CoffeeCapsuleHolder"]], "boxes.generators.coindisplay": [[15, 0, 1, "", "CoinDisplay"]], "boxes.generators.concaveknob": [[15, 0, 1, "", "ConcaveKnob"]], "boxes.generators.console": [[15, 0, 1, "", "Console"]], "boxes.generators.console2": [[15, 0, 1, "", "Console2"]], "boxes.generators.dicebox": [[15, 0, 1, "", "DiceBox"]], "boxes.generators.dinrailbox": [[15, 0, 1, "", "DinRailBox"]], "boxes.generators.discrack": [[15, 0, 1, "", "DiscRack"]], "boxes.generators.dispenser": [[15, 0, 1, "", "Dispenser"]], "boxes.generators.display": [[15, 0, 1, "", "Display"]], "boxes.generators.displaycase": [[15, 0, 1, "", "DisplayCase"]], "boxes.generators.displayshelf": [[15, 0, 1, "", "DisplayShelf"]], "boxes.generators.dividertray": [[15, 0, 1, "", "DividerTray"]], "boxes.generators.doubleflexdoorbox": [[15, 0, 1, "", "DoubleFlexDoorBox"]], "boxes.generators.drillbox": [[15, 0, 1, "", "DrillBox"]], "boxes.generators.drillstand": [[15, 0, 1, "", "DrillStand"]], "boxes.generators.electronicsbox": [[15, 0, 1, "", "ElectronicsBox"]], "boxes.generators.eurorackskiff": [[15, 0, 1, "", "EuroRackSkiff"]], "boxes.generators.fanhole": [[15, 0, 1, "", "FanHole"]], "boxes.generators.filltest": [[15, 0, 1, "", "FillTest"]], "boxes.generators.flexbox": [[15, 0, 1, "", "FlexBox"]], "boxes.generators.flexbox2": [[15, 0, 1, "", "FlexBox2"]], "boxes.generators.flexbox3": [[15, 0, 1, "", "FlexBox3"]], "boxes.generators.flexbox4": [[15, 0, 1, "", "FlexBox4"]], "boxes.generators.flexbox5": [[15, 0, 1, "", "FlexBox5"]], "boxes.generators.flextest": [[15, 0, 1, "", "FlexTest"]], "boxes.generators.flextest2": [[15, 0, 1, "", "FlexTest2"]], "boxes.generators.folder": [[15, 0, 1, "", "Folder"]], "boxes.generators.gear": [[15, 0, 1, "", "Gears"]], "boxes.generators.gearbox": [[15, 0, 1, "", "GearBox"]], "boxes.generators.halfbox": [[15, 0, 1, "", "HalfBox"]], "boxes.generators.heart": [[15, 0, 1, "", "HeartBox"]], "boxes.generators.hingebox": [[15, 0, 1, "", "HingeBox"]], "boxes.generators.holepattern": [[15, 0, 1, "", "HolePattern"]], "boxes.generators.hooks": [[15, 0, 1, "", "Hook"]], "boxes.generators.integratedhingebox": [[15, 0, 1, "", "IntegratedHingeBox"]], "boxes.generators.jointpanel": [[15, 0, 1, "", "JointPanel"]], "boxes.generators.keypad": [[15, 0, 1, "", "Keypad"]], "boxes.generators.laptopstand": [[15, 0, 1, "", "LaptopStand"]], "boxes.generators.laserclamp": [[15, 0, 1, "", "LaserClamp"]], "boxes.generators.laserholdfast": [[15, 0, 1, "", "LaserHoldfast"]], "boxes.generators.lbeam": [[15, 0, 1, "", "LBeam"]], "boxes.generators.magazinefile": [[15, 0, 1, "", "MagazinFile"]], "boxes.generators.makitapowersupply": [[15, 0, 1, "", "MakitaPowerSupply"]], "boxes.generators.microrack": [[15, 0, 1, "", "SBCMicroRack"]], "boxes.generators.nemamount": [[15, 0, 1, "", "NemaMount"]], "boxes.generators.nemapattern": [[15, 0, 1, "", "NemaPattern"]], "boxes.generators.notesholder": [[15, 0, 1, "", "NotesHolder"]], "boxes.generators.openbox": [[15, 0, 1, "", "OpenBox"]], "boxes.generators.organpipe": [[15, 0, 1, "", "OrganPipe"]], "boxes.generators.ottobody": [[15, 0, 1, "", "OttoBody"]], "boxes.generators.ottolegs": [[15, 0, 1, "", "OttoLegs"]], "boxes.generators.ottosoles": [[15, 0, 1, "", "OttoSoles"]], "boxes.generators.paintbox": [[15, 0, 1, "", "PaintStorage"]], "boxes.generators.paperbox": [[15, 0, 1, "", "PaperBox"]], "boxes.generators.phoneholder": [[15, 0, 1, "", "PhoneHolder"]], "boxes.generators.planetary": [[15, 0, 1, "", "Planetary"]], "boxes.generators.planetary2": [[15, 0, 1, "", "Planetary2"]], "boxes.generators.platonic": [[15, 0, 1, "", "Platonic"]], "boxes.generators.polehook": [[15, 0, 1, "", "PoleHook"]], "boxes.generators.pulley": [[15, 0, 1, "", "Pulley"]], "boxes.generators.rack10box": [[15, 0, 1, "", "Rack10Box"]], "boxes.generators.rack19box": [[15, 0, 1, "", "Rack19Box"]], "boxes.generators.rack19halfwidth": [[15, 0, 1, "", "Rack19HalfWidth"]], "boxes.generators.rackbox": [[15, 0, 1, "", "RackBox"]], "boxes.generators.rectangularWall": [[15, 0, 1, "", "RectangularWall"]], "boxes.generators.regularbox": [[15, 0, 1, "", "RegularBox"]], "boxes.generators.regularstarbox": [[15, 0, 1, "", "RegularStarBox"]], "boxes.generators.robotarm": [[15, 0, 1, "", "RobotArm"]], "boxes.generators.rotary": [[15, 0, 1, "", "Rotary"]], "boxes.generators.roundedbox": [[15, 0, 1, "", "RoundedBox"]], "boxes.generators.royalgame": [[15, 0, 1, "", "RoyalGame"]], "boxes.generators.shutterbox": [[15, 0, 1, "", "ShutterBox"]], "boxes.generators.sidedoorhousing": [[15, 0, 1, "", "SideDoorHousing"]], "boxes.generators.silverwarebox": [[15, 0, 1, "", "Silverware"]], "boxes.generators.slidingdrawer": [[15, 0, 1, "", "SlidingDrawer"]], "boxes.generators.spicesrack": [[15, 0, 1, "", "SpicesRack"]], "boxes.generators.stachel": [[15, 0, 1, "", "Stachel"]], "boxes.generators.storagerack": [[15, 0, 1, "", "StorageRack"]], "boxes.generators.storageshelf": [[15, 0, 1, "", "StorageShelf"]], "boxes.generators.trafficlight": [[15, 0, 1, "", "TrafficLight"]], "boxes.generators.trayinsert": [[15, 0, 1, "", "TrayInsert"]], "boxes.generators.traylayout": [[15, 0, 1, "", "TrayLayout"], [15, 0, 1, "", "TrayLayout2"]], "boxes.generators.trianglelamp": [[15, 0, 1, "", "TriangleLamp"]], "boxes.generators.two_piece": [[15, 0, 1, "", "TwoPiece"]], "boxes.generators.typetray": [[15, 0, 1, "", "TypeTray"]], "boxes.generators.ubox": [[15, 0, 1, "", "UBox"]], "boxes.generators.unevenheightbox": [[15, 0, 1, "", "UnevenHeightBox"]], "boxes.generators.universalbox": [[15, 0, 1, "", "UniversalBox"]], "boxes.generators.waivyknob": [[15, 0, 1, "", "WaivyKnob"]], "boxes.generators.wallcaliperholder": [[15, 0, 1, "", "WallCaliper"]], "boxes.generators.wallchiselholder": [[15, 0, 1, "", "WallChiselHolder"]], "boxes.generators.wallconsole": [[15, 0, 1, "", "WallConsole"]], "boxes.generators.walldrillbox": [[15, 0, 1, "", "WallDrillBox"]], "boxes.generators.walledges": [[15, 0, 1, "", "WallEdges"]], "boxes.generators.wallpinrow": [[15, 0, 1, "", "WallPinRow"]], "boxes.generators.wallplaneholder": [[15, 0, 1, "", "WallPlaneHolder"]], "boxes.generators.wallpliersholder": [[15, 0, 1, "", "WallPliersHolder"]], "boxes.generators.wallslottedholder": [[15, 0, 1, "", "WallSlottedHolder"]], "boxes.generators.wallstairs": [[15, 0, 1, "", "WallStairs"]], "boxes.generators.walltypetray": [[15, 0, 1, "", "WallTypeTray"]], "boxes.generators.wallwrenchholder": [[15, 0, 1, "", "WallWrenchHolder"]], "boxes.generators.winerack": [[15, 0, 1, "", "WineRack"]], "boxes.parts": [[13, 0, 1, "", "Parts"], [13, 4, 1, "", "arcOnCircle"]], "boxes.parts.Parts": [[13, 1, 1, "", "concaveKnob"], [13, 1, 1, "", "disc"], [13, 1, 1, "", "ringSegment"], [13, 1, 1, "", "waivyKnob"]], "boxes.pulley": [[13, 0, 1, "", "Pulley"], [13, 4, 1, "", "mirrorx"], [13, 4, 1, "", "tooth_spaceing_curvefit"], [13, 4, 1, "", "tooth_spacing"]], "boxes.pulley.Pulley": [[13, 1, 1, "", "diameter"], [13, 1, 1, "", "drawPoints"], [13, 1, 1, "", "getProfiles"], [13, 3, 1, "", "profile_data"], [13, 3, 1, "", "spacing"], [13, 3, 1, "", "teeth"]], "boxes.robot": [[13, 0, 1, "", "RobotArg"], [13, 0, 1, "", "RobotArmMM"], [13, 0, 1, "", "RobotArmMm"], [13, 0, 1, "", "RobotArmMu"], [13, 0, 1, "", "RobotArmUU"], [13, 0, 1, "", "RobotArmUu"]], "boxes.robot.RobotArg": [[13, 1, 1, "", "choices"], [13, 1, 1, "", "html"]], "boxes.servos": [[13, 0, 1, "", "EyeEdge"], [13, 0, 1, "", "Servo"], [13, 0, 1, "", "Servo9g"], [13, 0, 1, "", "Servo9gt"], [13, 0, 1, "", "ServoArg"], [13, 4, 1, "", "buildEdges"]], "boxes.servos.EyeEdge": [[13, 3, 1, "", "char"], [13, 1, 1, "", "margin"], [13, 1, 1, "", "startwidth"]], "boxes.servos.Servo": [[13, 1, 1, "", "edges"]], "boxes.servos.Servo9g": [[13, 3, 1, "", "axle_pos"], [13, 1, 1, "", "bottom"], [13, 1, 1, "", "front"], [13, 3, 1, "", "height"], [13, 1, 1, "", "hinge_depth"], [13, 1, 1, "", "hinge_width"], [13, 3, 1, "", "length"], [13, 3, 1, "", "servo_axle"], [13, 1, 1, "", "top"], [13, 3, 1, "", "width"]], "boxes.servos.Servo9gt": [[13, 1, 1, "", "bottom"], [13, 1, 1, "", "front"], [13, 3, 1, "", "height"], [13, 1, 1, "", "top"]], "boxes.servos.ServoArg": [[13, 1, 1, "", "choices"], [13, 1, 1, "", "html"]], "boxes.svgutil": [[13, 4, 1, "", "getSizeInMM"], [13, 4, 1, "", "getViewBox"], [13, 4, 1, "", "svgMerge"], [13, 4, 1, "", "ticksPerMM"]], "boxes.vectors": [[13, 4, 1, "", "circlepoint"], [13, 4, 1, "", "dotproduct"], [13, 4, 1, "", "kerf"], [13, 4, 1, "", "mmul"], [13, 4, 1, "", "normalize"], [13, 4, 1, "", "rotm"], [13, 4, 1, "", "tangent"], [13, 4, 1, "", "vadd"], [13, 4, 1, "", "vclip"], [13, 4, 1, "", "vdiff"], [13, 4, 1, "", "vlength"], [13, 4, 1, "", "vorthogonal"], [13, 4, 1, "", "vscalmul"], [13, 4, 1, "", "vtransl"]]}, "objtypes": {"0": "py:class", "1": "py:method", "2": "py:module", "3": "py:attribute", "4": "py:function", "5": "py:property"}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "method", "Python method"], "2": ["py", "module", "Python module"], "3": ["py", "attribute", "Python attribute"], "4": ["py", "function", "Python function"], "5": ["py", "property", "Python property"]}, "titleterms": {"contribut": 0, "box": [0, 1, 12, 13, 14, 15, 16, 20, 21], "py": [0, 1, 12, 14, 16, 21], "write": 0, "code": 0, "new": 0, "gener": [0, 2, 3, 9, 13, 14, 15, 18], "ad": 0, "depend": 0, "improv": 0, "document": 0, "provid": 0, "photo": 0, "user": [0, 2], "interfac": [0, 2], "run": [0, 17], "report": 0, "bug": 0, "suggest": 0, "featur": [0, 1], "about": 1, "architectur": 2, "part": [2, 8, 11, 13, 14, 15], "callback": [2, 11], "navig": [2, 10], "turtl": [2, 5], "graphic": [2, 5], "edg": [2, 3, 6, 11, 13, 21], "simpl": 2, "draw": [2, 5, 14], "command": [2, 5], "back": 2, "end": 2, "argument": [3, 21], "style": 3, "default": [3, 21], "access": 3, "burn": [4, 21], "correct": 4, "programm": 4, "": 4, "perspect": 4, "replac": 4, "invert": 4, "arc": 4, "special": 5, "function": 5, "latch": 5, "grip": [5, 6], "tab": [5, 21], "support": 5, "hexagon": 5, "hole": [5, 15], "pattern": 5, "base": 6, "class": [6, 8], "set": [6, 14, 21], "straight": 6, "stackabl": [6, 21], "finger": [6, 21], "joint": [6, 21], "bed": 6, "bolt": 6, "dove": 6, "tail": 6, "flex": 6, "slot": 6, "compoundedg": 6, "hing": 6, "hingepin": 6, "exampl": 7, "hous": 7, "some": 7, "electron": 7, "nemamount": [7, 15], "displayshelf": [7, 15], "bintrai": [7, 15], "stachel": [7, 15], "exist": 8, "handl": 9, "The": 11, "paramet": [11, 21], "move": 11, "partsmatrix": 11, "us": [12, 14, 21], "api": 12, "packag": 13, "subpackag": 13, "submodul": 13, "color": [13, 21], "modul": 13, "format": [13, 21], "gear": [13, 15], "lid": 13, "mount": 13, "pullei": [13, 15], "robot": 13, "servo": 13, "svgutil": 13, "vector": 13, "content": 13, "frequent": 14, "ask": 14, "question": 14, "can": 14, "i": 14, "sell": 14, "creat": 14, "why": 14, "do": 14, "my": 14, "fit": 14, "togeth": 14, "bit": 14, "too": 14, "big": 14, "small": 14, "ar": 14, "total": 14, "wrong": 14, "size": 14, "tini": 14, "weird": 14, "loop": 14, "corner": 14, "realli": 14, "don": 14, "t": 14, "want": 14, "those": 14, "what": 14, "were": 14, "all": 15, "abox": 15, "angledbox": 15, "basedbox": 15, "bayonetbox": 15, "cardbox": 15, "closedbox": 15, "consol": 15, "console2": 15, "dicebox": 15, "displaycas": 15, "electronicsbox": 15, "eurorackskiff": 15, "halfbox": 15, "hingebox": 15, "integratedhingebox": 15, "keypad": 15, "noteshold": 15, "openbox": 15, "rack10box": 15, "rack19box": 15, "rack19halfwidth": 15, "rackbox": 15, "regularbox": 15, "regularstarbox": 15, "sidedoorh": 15, "slidingdraw": 15, "twopiec": 15, "unevenheightbox": 15, "universalbox": 15, "flexbox": 15, "doubleflexdoorbox": 15, "flexbox2": 15, "flexbox3": 15, "flexbox4": 15, "flexbox5": 15, "heartbox": 15, "roundedbox": 15, "shutterbox": 15, "ubox": 15, "trai": 15, "dividertrai": 15, "drillbox": 15, "trayinsert": 15, "traylayout": 15, "traylayout2": 15, "typetrai": 15, "shelf": 15, "cardhold": 15, "discrack": 15, "paintstorag": 15, "sbcmicrorack": 15, "spicesrack": 15, "storagerack": 15, "storageshelf": 15, "winerack": 15, "wallmount": 15, "dinrailbox": 15, "wallcalip": 15, "wallchiselhold": 15, "wallconsol": 15, "walldrillbox": 15, "walledg": 15, "wallpinrow": 15, "wallplanehold": 15, "wallpliershold": 15, "wallslottedhold": 15, "wallstair": 15, "walltypetrai": 15, "wallwrenchhold": 15, "fanhol": 15, "holepattern": 15, "nemapattern": 15, "burntest": 15, "concaveknob": 15, "filltest": 15, "flextest": 15, "flextest2": 15, "gearbox": 15, "lbeam": 15, "laserholdfast": 15, "planetari": 15, "rectangularwal": 15, "robotarm": 15, "waivyknob": 15, "misc": 15, "agricolainsert": 15, "alledg": 15, "angledcutjig": 15, "arcad": 15, "atreus21": 15, "bottlestack": 15, "bottletag": 15, "canstorag": 15, "coffeecapsulehold": 15, "coindisplai": 15, "dispens": 15, "displai": 15, "drillstand": 15, "folder": 15, "hook": 15, "jointpanel": 15, "laptopstand": 15, "laserclamp": 15, "magazinfil": 15, "makitapowersuppli": 15, "ottobodi": 15, "ottoleg": 15, "ottosol": 15, "paperbox": 15, "phonehold": 15, "polehook": 15, "royalgam": 15, "trafficlight": 15, "trianglelamp": 15, "unstabl": 15, "birdhous": 15, "breadbox": 15, "castl": 15, "organpip": 15, "planetary2": 15, "platon": 15, "rotari": 15, "silverwar": 15, "indic": 16, "tabl": 16, "instal": 17, "requir": 17, "affin": 17, "shape": 17, "markdown": 17, "setuptool": 17, "ps2edit": 17, "python": 17, "sphinx": 17, "from": 17, "work": 17, "dir": 17, "inkscap": [17, 18], "platform": 17, "specif": 17, "instruct": 17, "maco": 18, "system": 18, "wide": 18, "extens": 18, "troubleshoot": 18, "window": 19, "nativ": 19, "subsystem": 19, "linux": 19, "unit": 21, "measur": 21, "thick": 21, "inner_corn": 21, "debug": 21, "refer": 21, "common": 21, "type": 21, "section": 21, "mounting_hol": 21, "outsid": 21}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 8, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.viewcode": 1, "sphinx": 57}, "alltitles": {"Contributing to Boxes.py": [[0, "contributing-to-boxes-py"]], "Writing code for Boxes.py": [[0, "writing-code-for-boxes-py"]], "Writing new Generators": [[0, "writing-new-generators"]], "Adding new Dependencies": [[0, "adding-new-dependencies"]], "Improving the Documentation": [[0, "improving-the-documentation"]], "Provide photos for generators": [[0, "provide-photos-for-generators"]], "Improving the User Interface": [[0, "improving-the-user-interface"]], "Running the Code": [[0, "running-the-code"]], "Reporting bugs": [[0, "reporting-bugs"]], "Suggesting new generators or features": [[0, "suggesting-new-generators-or-features"]], "About Boxes.py": [[1, "about-boxes-py"]], "Features": [[1, "features"]], "Architecture": [[2, "architecture"]], "User Interfaces": [[2, "user-interfaces"]], "Generators": [[2, "generators"], [9, "generators"]], "Parts": [[2, "parts"], [11, "parts"]], "Part Callbacks": [[2, "part-callbacks"]], "Navigation and Turtle Graphics": [[2, "navigation-and-turtle-graphics"]], "Edges": [[2, "edges"], [6, "edges"]], "Turtle graphics": [[2, "turtle-graphics"]], "Simple drawing commands": [[2, "simple-drawing-commands"]], "Back end": [[2, "back-end"]], "Generator Arguments": [[3, "generator-arguments"]], "Edge style arguments": [[3, "edge-style-arguments"]], "Default Arguments": [[3, "default-arguments"]], "Accessing the Arguments": [[3, "accessing-the-arguments"]], "Burn correction": [[4, "burn-correction"]], "Programmer\u2019s perspective": [[4, "programmer-s-perspective"]], "Replacing the inverted arcs": [[4, "replacing-the-inverted-arcs"]], "Drawing commands": [[5, "drawing-commands"]], "Turtle Graphics commands": [[5, "turtle-graphics-commands"]], "Special Functions": [[5, "special-functions"]], "Latch and Grip": [[5, "latch-and-grip"]], "Tab support": [[5, "tab-support"]], "Draw Commands": [[5, "draw-commands"]], "Hexagonal Hole patterns": [[5, "hexagonal-hole-patterns"]], "Edge base class": [[6, "edge-base-class"]], "Settings Class": [[6, "settings-class"]], "Straight Edges": [[6, "straight-edges"]], "Grip": [[6, "grip"]], "Stackable Edges": [[6, "stackable-edges"]], "Stackable Edge Settings": [[6, "stackable-edge-settings"], [21, "stackable-edge-settings"]], "Finger joints": [[6, "finger-joints"]], "Finger Joint Settings": [[6, "finger-joint-settings"], [21, "finger-joint-settings"]], "Bed Bolts": [[6, "bed-bolts"]], "Dove Tail Joints": [[6, "dove-tail-joints"]], "Dove Tail Settings": [[6, "dove-tail-settings"]], "Flex": [[6, "flex"]], "Flex Settings": [[6, "flex-settings"]], "Slots": [[6, "slots"]], "CompoundEdge": [[6, "compoundedge"]], "Hinges": [[6, "hinges"]], "Hinge Settings": [[6, "hinge-settings"]], "Hinge": [[6, "hinge"]], "HingePin": [[6, "hingepin"]], "Examples": [[7, "examples"]], "Housing for some electronics": [[7, "housing-for-some-electronics"]], "NemaMount": [[7, "nemamount"], [15, "nemamount"]], "DisplayShelf": [[7, "displayshelf"], [15, "displayshelf"]], "BinTray": [[7, "bintray"], [15, "bintray"]], "Stachel": [[7, "stachel"], [15, "stachel"]], "Existing Parts": [[8, "existing-parts"]], "Parts Class": [[8, "parts-class"]], "Handling Generators": [[9, "handling-generators"]], "Navigation": [[10, "navigation"]], "The callback parameter": [[11, "the-callback-parameter"]], "The move parameter": [[11, "the-move-parameter"]], "The edges parameter": [[11, "the-edges-parameter"]], "PartsMatrix": [[11, "partsmatrix"]], "Using the Boxes.py API": [[12, "using-the-boxes-py-api"]], "boxes package": [[13, "boxes-package"]], "Subpackage boxes.generators": [[13, "module-boxes.generators"]], "Submodules": [[13, "submodules"]], "boxes.Color module": [[13, "module-boxes.Color"]], "boxes.edges module": [[13, "module-boxes.edges"]], "boxes.formats module": [[13, "module-boxes.formats"]], "boxes.gears module": [[13, "module-boxes.gears"]], "boxes.lids module": [[13, "module-boxes.lids"]], "boxes.mounts module": [[13, "boxes-mounts-module"]], "boxes.parts module": [[13, "module-boxes.parts"]], "boxes.pulley module": [[13, "module-boxes.pulley"]], "boxes.robot module": [[13, "module-boxes.robot"]], "boxes.servos module": [[13, "module-boxes.servos"]], "boxes.svgutil module": [[13, "module-boxes.svgutil"]], "boxes.vectors module": [[13, "module-boxes.vectors"]], "Module contents": [[13, "module-boxes"]], "Frequently Asked Questions": [[14, "frequently-asked-questions"]], "Can I sell boxes I created with Boxes.py": [[14, "can-i-sell-boxes-i-created-with-boxes-py"]], "Why do my parts not fit together?": [[14, "why-do-my-parts-not-fit-together"]], "Why is my box a bit too big?": [[14, "why-is-my-box-a-bit-too-big"]], "Why is my box a bit too small?": [[14, "why-is-my-box-a-bit-too-small"]], "Why are my parts in the totally wrong size?": [[14, "why-are-my-parts-in-the-totally-wrong-size"]], "Why are there tiny, weird loops in the corners?": [[14, "why-are-there-tiny-weird-loops-in-the-corners"]], "I really don\u2019t want those weird, tiny loops?": [[14, "i-really-don-t-want-those-weird-tiny-loops"]], "What settings were used to generate a drawing?": [[14, "what-settings-were-used-to-generate-a-drawing"]], "All Box Generators": [[15, "all-box-generators"]], "Box": [[15, "box"]], "ABox": [[15, "abox"]], "AngledBox": [[15, "angledbox"]], "BasedBox": [[15, "basedbox"]], "BayonetBox": [[15, "bayonetbox"]], "CardBox": [[15, "cardbox"]], "ClosedBox": [[15, "closedbox"]], "Console": [[15, "console"]], "Console2": [[15, "console2"]], "DiceBox": [[15, "dicebox"]], "DisplayCase": [[15, "displaycase"]], "ElectronicsBox": [[15, "electronicsbox"]], "EuroRackSkiff": [[15, "eurorackskiff"]], "HalfBox": [[15, "halfbox"]], "HingeBox": [[15, "hingebox"]], "IntegratedHingeBox": [[15, "integratedhingebox"]], "Keypad": [[15, "keypad"]], "NotesHolder": [[15, "notesholder"]], "OpenBox": [[15, "openbox"]], "Rack10Box": [[15, "rack10box"]], "Rack19Box": [[15, "rack19box"]], "Rack19HalfWidth": [[15, "rack19halfwidth"]], "RackBox": [[15, "rackbox"]], "RegularBox": [[15, "regularbox"]], "RegularStarBox": [[15, "regularstarbox"]], "SideDoorHousing": [[15, "sidedoorhousing"]], "SlidingDrawer": [[15, "slidingdrawer"]], "TwoPiece": [[15, "twopiece"]], "UnevenHeightBox": [[15, "unevenheightbox"]], "UniversalBox": [[15, "universalbox"]], "FlexBox": [[15, "flexbox"], [15, "id1"]], "DoubleFlexDoorBox": [[15, "doubleflexdoorbox"]], "FlexBox2": [[15, "flexbox2"]], "FlexBox3": [[15, "flexbox3"]], "FlexBox4": [[15, "flexbox4"]], "FlexBox5": [[15, "flexbox5"]], "HeartBox": [[15, "heartbox"]], "RoundedBox": [[15, "roundedbox"]], "ShutterBox": [[15, "shutterbox"]], "UBox": [[15, "ubox"]], "Tray": [[15, "tray"]], "DividerTray": [[15, "dividertray"]], "DrillBox": [[15, "drillbox"]], "TrayInsert": [[15, "trayinsert"]], "TrayLayout": [[15, "traylayout"]], "TrayLayout2": [[15, "traylayout2"]], "TypeTray": [[15, "typetray"]], "Shelf": [[15, "shelf"]], "CardHolder": [[15, "cardholder"]], "DiscRack": [[15, "discrack"]], "PaintStorage": [[15, "paintstorage"]], "SBCMicroRack": [[15, "sbcmicrorack"]], "SpicesRack": [[15, "spicesrack"]], "StorageRack": [[15, "storagerack"]], "StorageShelf": [[15, "storageshelf"]], "WineRack": [[15, "winerack"]], "WallMounted": [[15, "wallmounted"]], "DinRailBox": [[15, "dinrailbox"]], "WallCaliper": [[15, "wallcaliper"]], "WallChiselHolder": [[15, "wallchiselholder"]], "WallConsole": [[15, "wallconsole"]], "WallDrillBox": [[15, "walldrillbox"]], "WallEdges": [[15, "walledges"]], "WallPinRow": [[15, "wallpinrow"]], "WallPlaneHolder": [[15, "wallplaneholder"]], "WallPliersHolder": [[15, "wallpliersholder"]], "WallSlottedHolder": [[15, "wallslottedholder"]], "WallStairs": [[15, "wallstairs"]], "WallTypeTray": [[15, "walltypetray"]], "WallWrenchHolder": [[15, "wallwrenchholder"]], "Holes": [[15, "holes"]], "FanHole": [[15, "fanhole"]], "HolePattern": [[15, "holepattern"]], "NemaPattern": [[15, "nemapattern"]], "Part": [[15, "part"]], "BurnTest": [[15, "burntest"]], "ConcaveKnob": [[15, "concaveknob"]], "FillTest": [[15, "filltest"]], "FlexTest": [[15, "flextest"]], "FlexTest2": [[15, "flextest2"]], "GearBox": [[15, "gearbox"]], "Gears": [[15, "gears"]], "LBeam": [[15, "lbeam"]], "LaserHoldfast": [[15, "laserholdfast"]], "Planetary": [[15, "planetary"]], "Pulley": [[15, "pulley"]], "RectangularWall": [[15, "rectangularwall"]], "RobotArm": [[15, "robotarm"]], "WaivyKnob": [[15, "waivyknob"]], "Misc": [[15, "misc"]], "AgricolaInsert": [[15, "agricolainsert"]], "AllEdges": [[15, "alledges"]], "AngledCutJig": [[15, "angledcutjig"]], "Arcade": [[15, "arcade"]], "Atreus21": [[15, "atreus21"]], "BottleStack": [[15, "bottlestack"]], "BottleTag": [[15, "bottletag"]], "CanStorage": [[15, "canstorage"]], "CoffeeCapsuleHolder": [[15, "coffeecapsuleholder"]], "CoinDisplay": [[15, "coindisplay"]], "Dispenser": [[15, "dispenser"]], "Display": [[15, "display"]], "DrillStand": [[15, "drillstand"]], "Folder": [[15, "folder"]], "Hook": [[15, "hook"]], "JointPanel": [[15, "jointpanel"]], "LaptopStand": [[15, "laptopstand"]], "LaserClamp": [[15, "laserclamp"]], "MagazinFile": [[15, "magazinfile"]], "MakitaPowerSupply": [[15, "makitapowersupply"]], "OttoBody": [[15, "ottobody"]], "OttoLegs": [[15, "ottolegs"]], "OttoSoles": [[15, "ottosoles"]], "PaperBox": [[15, "paperbox"]], "PhoneHolder": [[15, "phoneholder"]], "PoleHook": [[15, "polehook"]], "RoyalGame": [[15, "royalgame"]], "TrafficLight": [[15, "trafficlight"]], "TriangleLamp": [[15, "trianglelamp"]], "Unstable": [[15, "unstable"]], "BirdHouse": [[15, "birdhouse"]], "BreadBox": [[15, "breadbox"]], "Castle": [[15, "castle"]], "OrganPipe": [[15, "organpipe"]], "Planetary2": [[15, "planetary2"]], "Platonic": [[15, "platonic"]], "Rotary": [[15, "rotary"]], "Silverware": [[15, "silverware"]], "Boxes.py": [[16, "boxes-py"]], "Indices and tables": [[16, "indices-and-tables"]], "Installation": [[17, "installation"]], "Requirements": [[17, "requirements"]], "Affine": [[17, "affine"]], "Shapely": [[17, "shapely"]], "Markdown": [[17, "markdown"]], "setuptools": [[17, "setuptools"]], "ps2edit": [[17, "ps2edit"]], "Python": [[17, "python"]], "Sphinx": [[17, "sphinx"]], "Running from working dir": [[17, "running-from-working-dir"]], "Inkscape": [[17, "inkscape"]], "Platform specific instructions": [[17, "platform-specific-instructions"]], "macOS": [[18, "macos"]], "General": [[18, "general"]], "System-wide with Inkscape extension": [[18, "system-wide-with-inkscape-extension"]], "Troubleshooting": [[18, "troubleshooting"]], "Windows": [[19, "windows"]], "Native": [[19, "native"]], "Windows Subsystem for Linux": [[19, "windows-subsystem-for-linux"]], "boxes": [[20, "boxes"]], "Using Boxes.py": [[21, "using-boxes-py"]], "Units of measurements": [[21, "units-of-measurements"]], "Default arguments": [[21, "default-arguments"]], "thickness": [[21, "thickness"]], "burn": [[21, "burn"]], "format": [[21, "format"]], "tabs": [[21, "tabs"]], "inner_corners": [[21, "inner-corners"]], "debug": [[21, "debug"]], "reference": [[21, "reference"]], "Common Parameters and Types": [[21, "common-parameters-and-types"]], "Section parameters": [[21, "section-parameters"]], "mounting_holes": [[21, "mounting-holes"]], "outside": [[21, "outside"]], "Edge Type parameters": [[21, "edge-type-parameters"]], "Colors": [[21, "colors"]]}, "indexentries": {"argparseedgetype (class in boxes)": [[3, "boxes.ArgparseEdgeType"], [13, "boxes.ArgparseEdgeType"]], "__init__() (boxes.generators._template.box method)": [[3, "boxes.generators._template.BOX.__init__"], [3, "id0"]], "add_argument() (argparse.argumentparser method)": [[3, "argparse.ArgumentParser.add_argument"]], "buildargparser() (boxes.boxes method)": [[3, "boxes.Boxes.buildArgParser"], [13, "boxes.Boxes.buildArgParser"]], "parserarguments() (boxes.edges.settings class method)": [[3, "boxes.edges.Settings.parserArguments"], [13, "boxes.edges.Settings.parserArguments"]], "nema() (boxes.boxes method)": [[5, "boxes.Boxes.NEMA"], [13, "boxes.Boxes.NEMA"]], "nuthole (built-in class)": [[5, "NutHole"]], "tx() (boxes.boxes method)": [[5, "boxes.Boxes.TX"], [13, "boxes.Boxes.TX"]], "bedbolthole() (boxes.boxes method)": [[5, "boxes.Boxes.bedBoltHole"], [13, "boxes.Boxes.bedBoltHole"]], "corner() (boxes.boxes method)": [[5, "boxes.Boxes.corner"], [13, "boxes.Boxes.corner"]], "curveto() (boxes.boxes method)": [[5, "boxes.Boxes.curveTo"], [13, "boxes.Boxes.curveTo"]], "dhole() (boxes.boxes method)": [[5, "boxes.Boxes.dHole"], [13, "boxes.Boxes.dHole"]], "edge() (boxes.boxes method)": [[5, "boxes.Boxes.edge"], [13, "boxes.Boxes.edge"]], "flathole() (boxes.boxes method)": [[5, "boxes.Boxes.flatHole"], [13, "boxes.Boxes.flatHole"]], "flex2d() (boxes.boxes method)": [[5, "boxes.Boxes.flex2D"], [13, "boxes.Boxes.flex2D"]], "grip() (boxes.boxes method)": [[5, "boxes.Boxes.grip"], [13, "boxes.Boxes.grip"]], "handle() (boxes.boxes method)": [[5, "boxes.Boxes.handle"], [13, "boxes.Boxes.handle"]], "hexholescircle() (boxes.boxes method)": [[5, "boxes.Boxes.hexHolesCircle"], [13, "boxes.Boxes.hexHolesCircle"]], "hexholeshex() (boxes.boxes method)": [[5, "boxes.Boxes.hexHolesHex"], [13, "boxes.Boxes.hexHolesHex"]], "hexholesplate() (boxes.boxes method)": [[5, "boxes.Boxes.hexHolesPlate"], [13, "boxes.Boxes.hexHolesPlate"]], "hexholesrectangle() (boxes.boxes method)": [[5, "boxes.Boxes.hexHolesRectangle"], [13, "boxes.Boxes.hexHolesRectangle"]], "hole() (boxes.boxes method)": [[5, "boxes.Boxes.hole"], [13, "boxes.Boxes.hole"]], "latch() (boxes.boxes method)": [[5, "boxes.Boxes.latch"], [13, "boxes.Boxes.latch"]], "polyline() (boxes.boxes method)": [[5, "boxes.Boxes.polyline"], [13, "boxes.Boxes.polyline"]], "rectangularhole() (boxes.boxes method)": [[5, "boxes.Boxes.rectangularHole"], [13, "boxes.Boxes.rectangularHole"]], "text() (boxes.boxes method)": [[5, "boxes.Boxes.text"], [13, "boxes.Boxes.text"]], "baseedge (class in boxes.edges)": [[6, "boxes.edges.BaseEdge"], [13, "boxes.edges.BaseEdge"]], "boltpolicy (class in boxes.edges)": [[6, "boxes.edges.BoltPolicy"], [13, "boxes.edges.BoltPolicy"]], "bolts (class in boxes.edges)": [[6, "boxes.edges.Bolts"], [13, "boxes.edges.Bolts"]], "compoundedge (class in boxes.edges)": [[6, "boxes.edges.CompoundEdge"], [13, "boxes.edges.CompoundEdge"]], "crossingfingerholeedge (class in boxes.edges)": [[6, "boxes.edges.CrossingFingerHoleEdge"], [13, "boxes.edges.CrossingFingerHoleEdge"]], "dovetailjoint (class in boxes.edges)": [[6, "boxes.edges.DoveTailJoint"], [13, "boxes.edges.DoveTailJoint"]], "dovetailjointcounterpart (class in boxes.edges)": [[6, "boxes.edges.DoveTailJointCounterPart"], [13, "boxes.edges.DoveTailJointCounterPart"]], "dovetailsettings (class in boxes.edges)": [[6, "boxes.edges.DoveTailSettings"], [13, "boxes.edges.DoveTailSettings"]], "edge (class in boxes.edges)": [[6, "boxes.edges.Edge"], [13, "boxes.edges.Edge"]], "fingerholeedge (class in boxes.edges)": [[6, "boxes.edges.FingerHoleEdge"], [13, "boxes.edges.FingerHoleEdge"]], "fingerholes (class in boxes.edges)": [[6, "boxes.edges.FingerHoles"], [13, "boxes.edges.FingerHoles"]], "fingerjointedge (class in boxes.edges)": [[6, "boxes.edges.FingerJointEdge"], [13, "boxes.edges.FingerJointEdge"]], "fingerjointedgecounterpart (class in boxes.edges)": [[6, "boxes.edges.FingerJointEdgeCounterPart"], [13, "boxes.edges.FingerJointEdgeCounterPart"]], "fingerjointsettings (class in boxes.edges)": [[6, "boxes.edges.FingerJointSettings"], [13, "boxes.edges.FingerJointSettings"]], "flexedge (class in boxes.edges)": [[6, "boxes.edges.FlexEdge"], [13, "boxes.edges.FlexEdge"]], "flexsettings (class in boxes.edges)": [[6, "boxes.edges.FlexSettings"], [13, "boxes.edges.FlexSettings"]], "gripsettings (class in boxes.edges)": [[6, "boxes.edges.GripSettings"], [13, "boxes.edges.GripSettings"]], "grippingedge (class in boxes.edges)": [[6, "boxes.edges.GrippingEdge"], [13, "boxes.edges.GrippingEdge"]], "hinge (class in boxes.edges)": [[6, "boxes.edges.Hinge"], [13, "boxes.edges.Hinge"]], "hingepin (class in boxes.edges)": [[6, "boxes.edges.HingePin"], [13, "boxes.edges.HingePin"]], "hingesettings (class in boxes.edges)": [[6, "boxes.edges.HingeSettings"], [13, "boxes.edges.HingeSettings"]], "outsetedge (class in boxes.edges)": [[6, "boxes.edges.OutSetEdge"], [13, "boxes.edges.OutSetEdge"]], "settings (class in boxes.edges)": [[6, "boxes.edges.Settings"], [13, "boxes.edges.Settings"]], "slot (class in boxes.edges)": [[6, "boxes.edges.Slot"], [13, "boxes.edges.Slot"]], "slottededge (class in boxes.edges)": [[6, "boxes.edges.SlottedEdge"], [13, "boxes.edges.SlottedEdge"]], "stackableedge (class in boxes.edges)": [[6, "boxes.edges.StackableEdge"], [13, "boxes.edges.StackableEdge"]], "stackableedgetop (class in boxes.edges)": [[6, "boxes.edges.StackableEdgeTop"], [13, "boxes.edges.StackableEdgeTop"]], "stackablesettings (class in boxes.edges)": [[6, "boxes.edges.StackableSettings"], [13, "boxes.edges.StackableSettings"]], "__call__() (boxes.edges.baseedge method)": [[6, "boxes.edges.BaseEdge.__call__"]], "checkvalues() (boxes.edges.fingerjointsettings method)": [[6, "boxes.edges.FingerJointSettings.checkValues"], [13, "boxes.edges.FingerJointSettings.checkValues"]], "checkvalues() (boxes.edges.settings method)": [[6, "boxes.edges.Settings.checkValues"], [13, "boxes.edges.Settings.checkValues"]], "checkvalues() (boxes.edges.stackablesettings method)": [[6, "boxes.edges.StackableSettings.checkValues"], [13, "boxes.edges.StackableSettings.checkValues"]], "edgeobjects() (boxes.edges.dovetailsettings method)": [[6, "boxes.edges.DoveTailSettings.edgeObjects"], [13, "boxes.edges.DoveTailSettings.edgeObjects"]], "edgeobjects() (boxes.edges.fingerjointsettings method)": [[6, "boxes.edges.FingerJointSettings.edgeObjects"], [13, "boxes.edges.FingerJointSettings.edgeObjects"]], "edgeobjects() (boxes.edges.settings method)": [[6, "boxes.edges.Settings.edgeObjects"], [13, "boxes.edges.Settings.edgeObjects"]], "edgeobjects() (boxes.edges.stackablesettings method)": [[6, "boxes.edges.StackableSettings.edgeObjects"], [13, "boxes.edges.StackableSettings.edgeObjects"]], "endangle() (boxes.edges.baseedge method)": [[6, "boxes.edges.BaseEdge.endAngle"], [13, "boxes.edges.BaseEdge.endAngle"]], "margin() (boxes.edges.baseedge method)": [[6, "boxes.edges.BaseEdge.margin"], [13, "boxes.edges.BaseEdge.margin"]], "setvalues() (boxes.edges.settings method)": [[6, "boxes.edges.Settings.setValues"], [13, "boxes.edges.Settings.setValues"]], "spacing() (boxes.edges.baseedge method)": [[6, "boxes.edges.BaseEdge.spacing"], [13, "boxes.edges.BaseEdge.spacing"]], "startangle() (boxes.edges.baseedge method)": [[6, "boxes.edges.BaseEdge.startAngle"], [13, "boxes.edges.BaseEdge.startAngle"]], "startwidth() (boxes.edges.baseedge method)": [[6, "boxes.edges.BaseEdge.startwidth"], [13, "boxes.edges.BaseEdge.startwidth"]], "binfrontedge (class in boxes.generators.bintray)": [[7, "boxes.generators.bintray.BinFrontEdge"]], "bintray (class in boxes.generators.bintray)": [[7, "boxes.generators.bintray.BinTray"], [15, "boxes.generators.bintray.BinTray"]], "displayshelf (class in boxes.generators.displayshelf)": [[7, "boxes.generators.displayshelf.DisplayShelf"], [15, "boxes.generators.displayshelf.DisplayShelf"]], "nemamount (class in boxes.generators.nemamount)": [[7, "boxes.generators.nemamount.NemaMount"], [15, "boxes.generators.nemamount.NemaMount"]], "stachel (class in boxes.generators.stachel)": [[7, "boxes.generators.stachel.Stachel"], [15, "boxes.generators.stachel.Stachel"]], "typetray (class in boxes.generators.typetray)": [[7, "boxes.generators.typetray.TypeTray"], [15, "boxes.generators.typetray.TypeTray"]], "concaveknob() (boxes.parts.parts method)": [[8, "boxes.parts.Parts.concaveKnob"], [13, "boxes.parts.Parts.concaveKnob"]], "disc() (boxes.parts.parts method)": [[8, "boxes.parts.Parts.disc"], [13, "boxes.parts.Parts.disc"]], "flangedwall() (boxes.boxes method)": [[8, "boxes.Boxes.flangedWall"], [13, "boxes.Boxes.flangedWall"]], "polygonwall() (boxes.boxes method)": [[8, "boxes.Boxes.polygonWall"], [13, "boxes.Boxes.polygonWall"]], "rectangulartriangle() (boxes.boxes method)": [[8, "boxes.Boxes.rectangularTriangle"], [13, "boxes.Boxes.rectangularTriangle"]], "rectangularwall() (boxes.boxes method)": [[8, "boxes.Boxes.rectangularWall"], [13, "boxes.Boxes.rectangularWall"]], "regularpolygonwall() (boxes.boxes method)": [[8, "boxes.Boxes.regularPolygonWall"], [13, "boxes.Boxes.regularPolygonWall"]], "ringsegment() (boxes.parts.parts method)": [[8, "boxes.parts.Parts.ringSegment"], [13, "boxes.parts.Parts.ringSegment"]], "roundedplate() (boxes.boxes method)": [[8, "boxes.Boxes.roundedPlate"], [13, "boxes.Boxes.roundedPlate"]], "surroundingwall() (boxes.boxes method)": [[8, "boxes.Boxes.surroundingWall"], [13, "boxes.Boxes.surroundingWall"]], "waivyknob() (boxes.parts.parts method)": [[8, "boxes.parts.Parts.waivyKnob"], [13, "boxes.parts.Parts.waivyKnob"]], "boxes (class in boxes)": [[9, "boxes.Boxes"], [13, "boxes.Boxes"]], "uigroup (class in boxes.generators)": [[9, "boxes.generators.UIGroup"], [13, "boxes.generators.UIGroup"]], "__init__() (boxes.boxes method)": [[9, "boxes.Boxes.__init__"]], "add() (boxes.generators.uigroup method)": [[9, "boxes.generators.UIGroup.add"], [13, "boxes.generators.UIGroup.add"]], "boxes.generators": [[9, "module-boxes.generators"], [13, "module-boxes.generators"]], "close() (boxes.boxes method)": [[9, "boxes.Boxes.close"], [13, "boxes.Boxes.close"]], "getallboxgenerators() (in module boxes.generators)": [[9, "boxes.generators.getAllBoxGenerators"], [13, "boxes.generators.getAllBoxGenerators"]], "getallgeneratormodules() (in module boxes.generators)": [[9, "boxes.generators.getAllGeneratorModules"], [13, "boxes.generators.getAllGeneratorModules"]], "image (boxes.generators.uigroup property)": [[9, "boxes.generators.UIGroup.image"], [13, "boxes.generators.UIGroup.image"]], "module": [[9, "module-boxes.generators"], [13, "module-boxes"], [13, "module-boxes.Color"], [13, "module-boxes.edges"], [13, "module-boxes.formats"], [13, "module-boxes.gears"], [13, "module-boxes.generators"], [13, "module-boxes.lids"], [13, "module-boxes.parts"], [13, "module-boxes.pulley"], [13, "module-boxes.robot"], [13, "module-boxes.servos"], [13, "module-boxes.svgutil"], [13, "module-boxes.vectors"]], "open() (boxes.boxes method)": [[9, "boxes.Boxes.open"], [13, "boxes.Boxes.open"]], "parseargs() (boxes.boxes method)": [[9, "boxes.Boxes.parseArgs"], [13, "boxes.Boxes.parseArgs"]], "render() (boxes.boxes method)": [[9, "boxes.Boxes.render"], [13, "boxes.Boxes.render"]], "thumbnail (boxes.generators.uigroup property)": [[9, "boxes.generators.UIGroup.thumbnail"], [13, "boxes.generators.UIGroup.thumbnail"]], "movearc() (boxes.boxes method)": [[10, "boxes.Boxes.moveArc"], [13, "boxes.Boxes.moveArc"]], "moveto() (boxes.boxes method)": [[10, "boxes.Boxes.moveTo"], [13, "boxes.Boxes.moveTo"]], "saved_context() (boxes.boxes method)": [[10, "boxes.Boxes.saved_context"], [13, "boxes.Boxes.saved_context"]], "cc() (boxes.boxes method)": [[11, "boxes.Boxes.cc"], [13, "boxes.Boxes.cc"]], "getentry() (boxes.boxes method)": [[11, "boxes.Boxes.getEntry"], [13, "boxes.Boxes.getEntry"]], "move() (boxes.boxes method)": [[11, "boxes.Boxes.move"], [13, "boxes.Boxes.move"]], "partsmatrix() (boxes.boxes method)": [[11, "boxes.Boxes.partsMatrix"], [13, "boxes.Boxes.partsMatrix"]], "annotations (boxes.color.color attribute)": [[13, "boxes.Color.Color.ANNOTATIONS"]], "black (boxes.color.color attribute)": [[13, "boxes.Color.Color.BLACK"]], "blue (boxes.color.color attribute)": [[13, "boxes.Color.Color.BLUE"]], "boolarg (class in boxes)": [[13, "boxes.BoolArg"]], "cyan (boxes.color.color attribute)": [[13, "boxes.Color.Color.CYAN"]], "cabinethingeedge (class in boxes.edges)": [[13, "boxes.edges.CabinetHingeEdge"]], "cabinethingesettings (class in boxes.edges)": [[13, "boxes.edges.CabinetHingeSettings"]], "chesthinge (class in boxes.edges)": [[13, "boxes.edges.ChestHinge"]], "chesthingefront (class in boxes.edges)": [[13, "boxes.edges.ChestHingeFront"]], "chesthingepin (class in boxes.edges)": [[13, "boxes.edges.ChestHingePin"]], "chesthingesettings (class in boxes.edges)": [[13, "boxes.edges.ChestHingeSettings"]], "chesthingetop (class in boxes.edges)": [[13, "boxes.edges.ChestHingeTop"]], "clickconnector (class in boxes.edges)": [[13, "boxes.edges.ClickConnector"]], "clickedge (class in boxes.edges)": [[13, "boxes.edges.ClickEdge"]], "clicksettings (class in boxes.edges)": [[13, "boxes.edges.ClickSettings"]], "color (class in boxes.color)": [[13, "boxes.Color.Color"]], "etching (boxes.color.color attribute)": [[13, "boxes.Color.Color.ETCHING"]], "etching_deep (boxes.color.color attribute)": [[13, "boxes.Color.Color.ETCHING_DEEP"]], "eyeedge (class in boxes.servos)": [[13, "boxes.servos.EyeEdge"]], "fingerjointbase (class in boxes.edges)": [[13, "boxes.edges.FingerJointBase"]], "formats (class in boxes.formats)": [[13, "boxes.formats.Formats"]], "green (boxes.color.color attribute)": [[13, "boxes.Color.Color.GREEN"]], "gearsettings (class in boxes.edges)": [[13, "boxes.edges.GearSettings"]], "gears (class in boxes.gears)": [[13, "boxes.gears.Gears"]], "groovededge (class in boxes.edges)": [[13, "boxes.edges.GroovedEdge"]], "groovededgebase (class in boxes.edges)": [[13, "boxes.edges.GroovedEdgeBase"]], "groovededgecounterpart (class in boxes.edges)": [[13, "boxes.edges.GroovedEdgeCounterPart"]], "groovedsettings (class in boxes.edges)": [[13, "boxes.edges.GroovedSettings"]], "handleedge (class in boxes.edges)": [[13, "boxes.edges.HandleEdge"]], "handleedgesettings (class in boxes.edges)": [[13, "boxes.edges.HandleEdgeSettings"]], "handleholeedge (class in boxes.edges)": [[13, "boxes.edges.HandleHoleEdge"]], "hexholessettings (class in boxes)": [[13, "boxes.HexHolesSettings"]], "inner_cut (boxes.color.color attribute)": [[13, "boxes.Color.Color.INNER_CUT"]], "lidedge (class in boxes.edges)": [[13, "boxes.edges.LidEdge"]], "lidholeedge (class in boxes.edges)": [[13, "boxes.edges.LidHoleEdge"]], "lidleft (class in boxes.edges)": [[13, "boxes.edges.LidLeft"]], "lidright (class in boxes.edges)": [[13, "boxes.edges.LidRight"]], "lidsettings (class in boxes.edges)": [[13, "boxes.edges.LidSettings"]], "lidsideleft (class in boxes.edges)": [[13, "boxes.edges.LidSideLeft"]], "lidsideright (class in boxes.edges)": [[13, "boxes.edges.LidSideRight"]], "magenta (boxes.color.color attribute)": [[13, "boxes.Color.Color.MAGENTA"]], "mountingedge (class in boxes.edges)": [[13, "boxes.edges.MountingEdge"]], "mountingsettings (class in boxes.edges)": [[13, "boxes.edges.MountingSettings"]], "nuthole (class in boxes)": [[13, "boxes.NutHole"]], "outer_cut (boxes.color.color attribute)": [[13, "boxes.Color.Color.OUTER_CUT"]], "optionparser (class in boxes.gears)": [[13, "boxes.gears.OptionParser"]], "param_arc (boxes.edges.groovedsettings attribute)": [[13, "boxes.edges.GroovedSettings.PARAM_ARC"]], "param_back (boxes.edges.mountingsettings attribute)": [[13, "boxes.edges.MountingSettings.PARAM_BACK"]], "param_ext (boxes.edges.mountingsettings attribute)": [[13, "boxes.edges.MountingSettings.PARAM_EXT"]], "param_flat (boxes.edges.groovedsettings attribute)": [[13, "boxes.edges.GroovedSettings.PARAM_FLAT"]], "param_front (boxes.edges.mountingsettings attribute)": [[13, "boxes.edges.MountingSettings.PARAM_FRONT"]], "param_in (boxes.edges.mountingsettings attribute)": [[13, "boxes.edges.MountingSettings.PARAM_IN"]], "param_left (boxes.edges.mountingsettings attribute)": [[13, "boxes.edges.MountingSettings.PARAM_LEFT"]], "param_right (boxes.edges.mountingsettings attribute)": [[13, "boxes.edges.MountingSettings.PARAM_RIGHT"]], "param_softarc (boxes.edges.groovedsettings attribute)": [[13, "boxes.edges.GroovedSettings.PARAM_SOFTARC"]], "param_tab (boxes.edges.mountingsettings attribute)": [[13, "boxes.edges.MountingSettings.PARAM_TAB"]], "param_triangle (boxes.edges.groovedsettings attribute)": [[13, "boxes.edges.GroovedSettings.PARAM_TRIANGLE"]], "parts (class in boxes.parts)": [[13, "boxes.parts.Parts"]], "pulley (class in boxes.pulley)": [[13, "boxes.pulley.Pulley"]], "red (boxes.color.color attribute)": [[13, "boxes.Color.Color.RED"]], "rackedge (class in boxes.edges)": [[13, "boxes.edges.RackEdge"]], "robotarg (class in boxes.robot)": [[13, "boxes.robot.RobotArg"]], "robotarmmm (class in boxes.robot)": [[13, "boxes.robot.RobotArmMM"], [13, "boxes.robot.RobotArmMm"]], "robotarmmu (class in boxes.robot)": [[13, "boxes.robot.RobotArmMu"]], "robotarmuu (class in boxes.robot)": [[13, "boxes.robot.RobotArmUU"], [13, "boxes.robot.RobotArmUu"]], "roundedtriangleedge (class in boxes.edges)": [[13, "boxes.edges.RoundedTriangleEdge"]], "roundedtriangleedgesettings (class in boxes.edges)": [[13, "boxes.edges.RoundedTriangleEdgeSettings"]], "roundedtrianglefingerholesedge (class in boxes.edges)": [[13, "boxes.edges.RoundedTriangleFingerHolesEdge"]], "servo (class in boxes.servos)": [[13, "boxes.servos.Servo"]], "servo9g (class in boxes.servos)": [[13, "boxes.servos.Servo9g"]], "servo9gt (class in boxes.servos)": [[13, "boxes.servos.Servo9gt"]], "servoarg (class in boxes.servos)": [[13, "boxes.servos.ServoArg"]], "stackablebaseedge (class in boxes.edges)": [[13, "boxes.edges.StackableBaseEdge"]], "stackablefeet (class in boxes.edges)": [[13, "boxes.edges.StackableFeet"]], "stackableholeedgetop (class in boxes.edges)": [[13, "boxes.edges.StackableHoleEdgeTop"]], "white (boxes.color.color attribute)": [[13, "boxes.Color.Color.WHITE"]], "yellow (boxes.color.color attribute)": [[13, "boxes.Color.Color.YELLOW"]], "absolute_params (boxes.hexholessettings attribute)": [[13, "boxes.HexHolesSettings.absolute_params"]], "absolute_params (boxes.edges.cabinethingesettings attribute)": [[13, "boxes.edges.CabinetHingeSettings.absolute_params"]], "absolute_params (boxes.edges.chesthingesettings attribute)": [[13, "boxes.edges.ChestHingeSettings.absolute_params"]], "absolute_params (boxes.edges.clicksettings attribute)": [[13, "boxes.edges.ClickSettings.absolute_params"]], "absolute_params (boxes.edges.dovetailsettings attribute)": [[13, "boxes.edges.DoveTailSettings.absolute_params"]], "absolute_params (boxes.edges.fingerjointsettings attribute)": [[13, "boxes.edges.FingerJointSettings.absolute_params"]], "absolute_params (boxes.edges.flexsettings attribute)": [[13, "boxes.edges.FlexSettings.absolute_params"]], "absolute_params (boxes.edges.gearsettings attribute)": [[13, "boxes.edges.GearSettings.absolute_params"]], "absolute_params (boxes.edges.gripsettings attribute)": [[13, "boxes.edges.GripSettings.absolute_params"]], "absolute_params (boxes.edges.groovedsettings attribute)": [[13, "boxes.edges.GroovedSettings.absolute_params"]], "absolute_params (boxes.edges.handleedgesettings attribute)": [[13, "boxes.edges.HandleEdgeSettings.absolute_params"]], "absolute_params (boxes.edges.hingesettings attribute)": [[13, "boxes.edges.HingeSettings.absolute_params"]], "absolute_params (boxes.edges.lidsettings attribute)": [[13, "boxes.edges.LidSettings.absolute_params"]], "absolute_params (boxes.edges.mountingsettings attribute)": [[13, "boxes.edges.MountingSettings.absolute_params"]], "absolute_params (boxes.edges.roundedtriangleedgesettings attribute)": [[13, "boxes.edges.RoundedTriangleEdgeSettings.absolute_params"]], "absolute_params (boxes.edges.settings attribute)": [[13, "boxes.edges.Settings.absolute_params"]], "absolute_params (boxes.edges.stackablesettings attribute)": [[13, "boxes.edges.StackableSettings.absolute_params"]], "absolute_params (boxes.fillholessettings attribute)": [[13, "boxes.fillHolesSettings.absolute_params"]], "addpart() (boxes.boxes method)": [[13, "boxes.Boxes.addPart"]], "addparts() (boxes.boxes method)": [[13, "boxes.Boxes.addParts"]], "addsettingsargs() (boxes.boxes method)": [[13, "boxes.Boxes.addSettingsArgs"]], "add_option() (boxes.gears.optionparser method)": [[13, "boxes.gears.OptionParser.add_option"]], "adjustsize() (boxes.boxes method)": [[13, "boxes.Boxes.adjustSize"]], "arconcircle() (in module boxes.parts)": [[13, "boxes.parts.arcOnCircle"]], "argparsesections() (in module boxes)": [[13, "boxes.argparseSections"]], "argparsesections() (in module boxes.edges)": [[13, "boxes.edges.argparseSections"]], "axle_pos (boxes.servos.servo9g attribute)": [[13, "boxes.servos.Servo9g.axle_pos"]], "bottom (boxes.edges.stackablebaseedge attribute)": [[13, "boxes.edges.StackableBaseEdge.bottom"]], "bottom (boxes.edges.stackableedgetop attribute)": [[13, "boxes.edges.StackableEdgeTop.bottom"]], "bottom (boxes.edges.stackableholeedgetop attribute)": [[13, "boxes.edges.StackableHoleEdgeTop.bottom"]], "bottom() (boxes.servos.servo9g method)": [[13, "boxes.servos.Servo9g.bottom"]], "bottom() (boxes.servos.servo9gt method)": [[13, "boxes.servos.Servo9gt.bottom"]], "boxes": [[13, "module-boxes"]], "boxes.color": [[13, "module-boxes.Color"]], "boxes.edges": [[13, "module-boxes.edges"]], "boxes.formats": [[13, "module-boxes.formats"]], "boxes.gears": [[13, "module-boxes.gears"]], "boxes.lids": [[13, "module-boxes.lids"]], "boxes.parts": [[13, "module-boxes.parts"]], "boxes.pulley": [[13, "module-boxes.pulley"]], "boxes.robot": [[13, "module-boxes.robot"]], "boxes.servos": [[13, "module-boxes.servos"]], "boxes.svgutil": [[13, "module-boxes.svgutil"]], "boxes.vectors": [[13, "module-boxes.vectors"]], "buildedges() (in module boxes.servos)": [[13, "boxes.servos.buildEdges"]], "bumps() (boxes.edges.grippingedge method)": [[13, "boxes.edges.GrippingEdge.bumps"]], "calcfingers() (boxes.edges.fingerjointbase method)": [[13, "boxes.edges.FingerJointBase.calcFingers"]], "calc_circular_pitch() (boxes.gears.gears method)": [[13, "boxes.gears.Gears.calc_circular_pitch"]], "char (boxes.edges.baseedge attribute)": [[13, "boxes.edges.BaseEdge.char"]], "char (boxes.edges.cabinethingeedge attribute)": [[13, "boxes.edges.CabinetHingeEdge.char"]], "char (boxes.edges.chesthinge attribute)": [[13, "boxes.edges.ChestHinge.char"]], "char (boxes.edges.chesthingefront attribute)": [[13, "boxes.edges.ChestHingeFront.char"]], "char (boxes.edges.chesthingepin attribute)": [[13, "boxes.edges.ChestHingePin.char"]], "char (boxes.edges.chesthingetop attribute)": [[13, "boxes.edges.ChestHingeTop.char"]], "char (boxes.edges.clickconnector attribute)": [[13, "boxes.edges.ClickConnector.char"]], "char (boxes.edges.clickedge attribute)": [[13, "boxes.edges.ClickEdge.char"]], "char (boxes.edges.crossingfingerholeedge attribute)": [[13, "boxes.edges.CrossingFingerHoleEdge.char"]], "char (boxes.edges.dovetailjoint attribute)": [[13, "boxes.edges.DoveTailJoint.char"]], "char (boxes.edges.dovetailjointcounterpart attribute)": [[13, "boxes.edges.DoveTailJointCounterPart.char"]], "char (boxes.edges.edge attribute)": [[13, "boxes.edges.Edge.char"]], "char (boxes.edges.fingerholeedge attribute)": [[13, "boxes.edges.FingerHoleEdge.char"]], "char (boxes.edges.fingerjointedge attribute)": [[13, "boxes.edges.FingerJointEdge.char"]], "char (boxes.edges.fingerjointedgecounterpart attribute)": [[13, "boxes.edges.FingerJointEdgeCounterPart.char"]], "char (boxes.edges.flexedge attribute)": [[13, "boxes.edges.FlexEdge.char"]], "char (boxes.edges.grippingedge attribute)": [[13, "boxes.edges.GrippingEdge.char"]], "char (boxes.edges.groovededge attribute)": [[13, "boxes.edges.GroovedEdge.char"]], "char (boxes.edges.groovededgecounterpart attribute)": [[13, "boxes.edges.GroovedEdgeCounterPart.char"]], "char (boxes.edges.handleedge attribute)": [[13, "boxes.edges.HandleEdge.char"]], "char (boxes.edges.handleholeedge attribute)": [[13, "boxes.edges.HandleHoleEdge.char"]], "char (boxes.edges.hinge attribute)": [[13, "boxes.edges.Hinge.char"]], "char (boxes.edges.hingepin attribute)": [[13, "boxes.edges.HingePin.char"]], "char (boxes.edges.lidedge attribute)": [[13, "boxes.edges.LidEdge.char"]], "char (boxes.edges.lidholeedge attribute)": [[13, "boxes.edges.LidHoleEdge.char"]], "char (boxes.edges.lidleft attribute)": [[13, "boxes.edges.LidLeft.char"]], "char (boxes.edges.lidright attribute)": [[13, "boxes.edges.LidRight.char"]], "char (boxes.edges.lidsideleft attribute)": [[13, "boxes.edges.LidSideLeft.char"]], "char (boxes.edges.lidsideright attribute)": [[13, "boxes.edges.LidSideRight.char"]], "char (boxes.edges.mountingedge attribute)": [[13, "boxes.edges.MountingEdge.char"]], "char (boxes.edges.outsetedge attribute)": [[13, "boxes.edges.OutSetEdge.char"]], "char (boxes.edges.rackedge attribute)": [[13, "boxes.edges.RackEdge.char"]], "char (boxes.edges.roundedtriangleedge attribute)": [[13, "boxes.edges.RoundedTriangleEdge.char"]], "char (boxes.edges.roundedtrianglefingerholesedge attribute)": [[13, "boxes.edges.RoundedTriangleFingerHolesEdge.char"]], "char (boxes.edges.stackablebaseedge attribute)": [[13, "boxes.edges.StackableBaseEdge.char"]], "char (boxes.edges.stackableedge attribute)": [[13, "boxes.edges.StackableEdge.char"]], "char (boxes.edges.stackableedgetop attribute)": [[13, "boxes.edges.StackableEdgeTop.char"]], "char (boxes.edges.stackablefeet attribute)": [[13, "boxes.edges.StackableFeet.char"]], "char (boxes.edges.stackableholeedgetop attribute)": [[13, "boxes.edges.StackableHoleEdgeTop.char"]], "char (boxes.servos.eyeedge attribute)": [[13, "boxes.servos.EyeEdge.char"]], "checkvalues() (boxes.edges.chesthingesettings method)": [[13, "boxes.edges.ChestHingeSettings.checkValues"]], "checkvalues() (boxes.edges.flexsettings method)": [[13, "boxes.edges.FlexSettings.checkValues"]], "checkvalues() (boxes.edges.hingesettings method)": [[13, "boxes.edges.HingeSettings.checkValues"]], "choices() (boxes.robot.robotarg method)": [[13, "boxes.robot.RobotArg.choices"]], "choices() (boxes.servos.servoarg method)": [[13, "boxes.servos.ServoArg.choices"]], "circle() (boxes.boxes method)": [[13, "boxes.Boxes.circle"]], "circlepoint() (in module boxes.vectors)": [[13, "boxes.vectors.circlepoint"]], "convert() (boxes.formats.formats method)": [[13, "boxes.formats.Formats.convert"]], "description (boxes.boxes attribute)": [[13, "boxes.Boxes.description"]], "description (boxes.edges.baseedge attribute)": [[13, "boxes.edges.BaseEdge.description"]], "description (boxes.edges.cabinethingeedge attribute)": [[13, "boxes.edges.CabinetHingeEdge.description"]], "description (boxes.edges.chesthinge attribute)": [[13, "boxes.edges.ChestHinge.description"]], "description (boxes.edges.chesthingefront attribute)": [[13, "boxes.edges.ChestHingeFront.description"]], "description (boxes.edges.chesthingepin attribute)": [[13, "boxes.edges.ChestHingePin.description"]], "description (boxes.edges.clickconnector attribute)": [[13, "boxes.edges.ClickConnector.description"]], "description (boxes.edges.clickedge attribute)": [[13, "boxes.edges.ClickEdge.description"]], "description (boxes.edges.compoundedge attribute)": [[13, "boxes.edges.CompoundEdge.description"]], "description (boxes.edges.crossingfingerholeedge attribute)": [[13, "boxes.edges.CrossingFingerHoleEdge.description"]], "description (boxes.edges.dovetailjoint attribute)": [[13, "boxes.edges.DoveTailJoint.description"]], "description (boxes.edges.dovetailjointcounterpart attribute)": [[13, "boxes.edges.DoveTailJointCounterPart.description"]], "description (boxes.edges.edge attribute)": [[13, "boxes.edges.Edge.description"]], "description (boxes.edges.fingerholeedge attribute)": [[13, "boxes.edges.FingerHoleEdge.description"]], "description (boxes.edges.fingerjointedge attribute)": [[13, "boxes.edges.FingerJointEdge.description"]], "description (boxes.edges.fingerjointedgecounterpart attribute)": [[13, "boxes.edges.FingerJointEdgeCounterPart.description"]], "description (boxes.edges.flexedge attribute)": [[13, "boxes.edges.FlexEdge.description"]], "description (boxes.edges.grippingedge attribute)": [[13, "boxes.edges.GrippingEdge.description"]], "description (boxes.edges.groovededge attribute)": [[13, "boxes.edges.GroovedEdge.description"]], "description (boxes.edges.groovededgecounterpart attribute)": [[13, "boxes.edges.GroovedEdgeCounterPart.description"]], "description (boxes.edges.handleedge attribute)": [[13, "boxes.edges.HandleEdge.description"]], "description (boxes.edges.handleholeedge attribute)": [[13, "boxes.edges.HandleHoleEdge.description"]], "description (boxes.edges.hinge attribute)": [[13, "boxes.edges.Hinge.description"]], "description (boxes.edges.hingepin attribute)": [[13, "boxes.edges.HingePin.description"]], "description (boxes.edges.lidedge attribute)": [[13, "boxes.edges.LidEdge.description"]], "description (boxes.edges.lidholeedge attribute)": [[13, "boxes.edges.LidHoleEdge.description"]], "description (boxes.edges.lidleft attribute)": [[13, "boxes.edges.LidLeft.description"]], "description (boxes.edges.lidright attribute)": [[13, "boxes.edges.LidRight.description"]], "description (boxes.edges.lidsideleft attribute)": [[13, "boxes.edges.LidSideLeft.description"]], "description (boxes.edges.lidsideright attribute)": [[13, "boxes.edges.LidSideRight.description"]], "description (boxes.edges.mountingedge attribute)": [[13, "boxes.edges.MountingEdge.description"]], "description (boxes.edges.outsetedge attribute)": [[13, "boxes.edges.OutSetEdge.description"]], "description (boxes.edges.rackedge attribute)": [[13, "boxes.edges.RackEdge.description"]], "description (boxes.edges.roundedtriangleedge attribute)": [[13, "boxes.edges.RoundedTriangleEdge.description"]], "description (boxes.edges.slot attribute)": [[13, "boxes.edges.Slot.description"]], "description (boxes.edges.slottededge attribute)": [[13, "boxes.edges.SlottedEdge.description"]], "description (boxes.edges.stackablebaseedge attribute)": [[13, "boxes.edges.StackableBaseEdge.description"]], "description (boxes.edges.stackableedge attribute)": [[13, "boxes.edges.StackableEdge.description"]], "description (boxes.edges.stackableedgetop attribute)": [[13, "boxes.edges.StackableEdgeTop.description"]], "description (boxes.edges.stackablefeet attribute)": [[13, "boxes.edges.StackableFeet.description"]], "description (boxes.edges.stackableholeedgetop attribute)": [[13, "boxes.edges.StackableHoleEdgeTop.description"]], "diameter() (boxes.pulley.pulley method)": [[13, "boxes.pulley.Pulley.diameter"]], "dist() (in module boxes)": [[13, "boxes.dist"]], "dotproduct() (in module boxes.vectors)": [[13, "boxes.vectors.dotproduct"]], "drawbolt() (boxes.edges.bolts method)": [[13, "boxes.edges.Bolts.drawBolt"]], "drawpoints() (boxes.gears.gears method)": [[13, "boxes.gears.Gears.drawPoints"]], "drawpoints() (boxes.pulley.pulley method)": [[13, "boxes.pulley.Pulley.drawPoints"]], "draw_finger() (boxes.edges.fingerjointedge method)": [[13, "boxes.edges.FingerJointEdge.draw_finger"]], "drawbolt() (boxes.edges.boltpolicy method)": [[13, "boxes.edges.BoltPolicy.drawbolt"]], "edgecorner() (boxes.boxes method)": [[13, "boxes.Boxes.edgeCorner"]], "edgeobjects() (boxes.edges.cabinethingesettings method)": [[13, "boxes.edges.CabinetHingeSettings.edgeObjects"]], "edgeobjects() (boxes.edges.chesthingesettings method)": [[13, "boxes.edges.ChestHingeSettings.edgeObjects"]], "edgeobjects() (boxes.edges.clicksettings method)": [[13, "boxes.edges.ClickSettings.edgeObjects"]], "edgeobjects() (boxes.edges.gripsettings method)": [[13, "boxes.edges.GripSettings.edgeObjects"]], "edgeobjects() (boxes.edges.groovedsettings method)": [[13, "boxes.edges.GroovedSettings.edgeObjects"]], "edgeobjects() (boxes.edges.handleedgesettings method)": [[13, "boxes.edges.HandleEdgeSettings.edgeObjects"]], "edgeobjects() (boxes.edges.hingesettings method)": [[13, "boxes.edges.HingeSettings.edgeObjects"]], "edgeobjects() (boxes.edges.lidsettings method)": [[13, "boxes.edges.LidSettings.edgeObjects"]], "edgeobjects() (boxes.edges.mountingsettings method)": [[13, "boxes.edges.MountingSettings.edgeObjects"]], "edgeobjects() (boxes.edges.roundedtriangleedgesettings method)": [[13, "boxes.edges.RoundedTriangleEdgeSettings.edgeObjects"]], "edges (boxes.argparseedgetype attribute)": [[13, "boxes.ArgparseEdgeType.edges"]], "edges() (boxes.servos.servo method)": [[13, "boxes.servos.Servo.edges"]], "endwidth() (boxes.edges.baseedge method)": [[13, "boxes.edges.BaseEdge.endwidth"]], "endwidth() (boxes.edges.chesthinge method)": [[13, "boxes.edges.ChestHinge.endwidth"]], "endwidth() (boxes.edges.chesthingetop method)": [[13, "boxes.edges.ChestHingeTop.endwidth"]], "endwidth() (boxes.edges.compoundedge method)": [[13, "boxes.edges.CompoundEdge.endwidth"]], "endwidth() (boxes.edges.hingepin method)": [[13, "boxes.edges.HingePin.endwidth"]], "endwidth() (boxes.edges.lidright method)": [[13, "boxes.edges.LidRight.endwidth"]], "endwidth() (boxes.edges.lidsideright method)": [[13, "boxes.edges.LidSideRight.endwidth"]], "endwidth() (boxes.edges.slottededge method)": [[13, "boxes.edges.SlottedEdge.endwidth"]], "extra_height (boxes.edges.handleedge attribute)": [[13, "boxes.edges.HandleEdge.extra_height"]], "extra_height (boxes.edges.handleholeedge attribute)": [[13, "boxes.edges.HandleHoleEdge.extra_height"]], "fillholes() (boxes.boxes method)": [[13, "boxes.Boxes.fillHoles"]], "fillholessettings (class in boxes)": [[13, "boxes.fillHolesSettings"]], "finger() (boxes.edges.clickconnector method)": [[13, "boxes.edges.ClickConnector.finger"]], "fingerholerectangle() (boxes.boxes method)": [[13, "boxes.Boxes.fingerHoleRectangle"]], "fingerlength() (boxes.edges.fingerjointbase method)": [[13, "boxes.edges.FingerJointBase.fingerLength"]], "flush() (boxes.edges.hinge method)": [[13, "boxes.edges.Hinge.flush"]], "flush() (boxes.edges.hingepin method)": [[13, "boxes.edges.HingePin.flush"]], "flushlen() (boxes.edges.hinge method)": [[13, "boxes.edges.Hinge.flushlen"]], "flushlen() (boxes.edges.hingepin method)": [[13, "boxes.edges.HingePin.flushlen"]], "formats (boxes.formats.formats attribute)": [[13, "boxes.formats.Formats.formats"]], "front() (boxes.servos.servo9g method)": [[13, "boxes.servos.Servo9g.front"]], "front() (boxes.servos.servo9gt method)": [[13, "boxes.servos.Servo9gt.front"]], "gearcarrier() (boxes.gears.gears method)": [[13, "boxes.gears.Gears.gearCarrier"]], "gear_calculations() (in module boxes.gears)": [[13, "boxes.gears.gear_calculations"]], "generate_rack_points() (in module boxes.gears)": [[13, "boxes.gears.generate_rack_points"]], "generate_spokes() (boxes.gears.gears method)": [[13, "boxes.gears.Gears.generate_spokes"]], "generate_spur_points() (in module boxes.gears)": [[13, "boxes.gears.generate_spur_points"]], "getdescriptions() (in module boxes.edges)": [[13, "boxes.edges.getDescriptions"]], "getformats() (boxes.formats.formats method)": [[13, "boxes.formats.Formats.getFormats"]], "getprofiles() (boxes.pulley.pulley class method)": [[13, "boxes.pulley.Pulley.getProfiles"]], "getsizeinmm() (in module boxes.svgutil)": [[13, "boxes.svgutil.getSizeInMM"]], "getsurface() (boxes.formats.formats method)": [[13, "boxes.formats.Formats.getSurface"]], "getviewbox() (in module boxes.svgutil)": [[13, "boxes.svgutil.getViewBox"]], "groove_arc() (boxes.edges.groovededgebase method)": [[13, "boxes.edges.GroovedEdgeBase.groove_arc"]], "groove_soft_arc() (boxes.edges.groovededgebase method)": [[13, "boxes.edges.GroovedEdgeBase.groove_soft_arc"]], "groove_triangle() (boxes.edges.groovededgebase method)": [[13, "boxes.edges.GroovedEdgeBase.groove_triangle"]], "have_undercut() (in module boxes.gears)": [[13, "boxes.gears.have_undercut"]], "height (boxes.servos.servo9g attribute)": [[13, "boxes.servos.Servo9g.height"]], "height (boxes.servos.servo9gt attribute)": [[13, "boxes.servos.Servo9gt.height"]], "hinge_depth() (boxes.servos.servo9g method)": [[13, "boxes.servos.Servo9g.hinge_depth"]], "hinge_width() (boxes.servos.servo9g method)": [[13, "boxes.servos.Servo9g.hinge_width"]], "holecol() (in module boxes)": [[13, "boxes.holeCol"]], "hook() (boxes.edges.clickconnector method)": [[13, "boxes.edges.ClickConnector.hook"]], "hookoffset() (boxes.edges.clickconnector method)": [[13, "boxes.edges.ClickConnector.hookOffset"]], "hookwidth() (boxes.edges.clickconnector method)": [[13, "boxes.edges.ClickConnector.hookWidth"]], "html() (boxes.argparseedgetype method)": [[13, "boxes.ArgparseEdgeType.html"]], "html() (boxes.boolarg method)": [[13, "boxes.BoolArg.html"]], "html() (boxes.robot.robotarg method)": [[13, "boxes.robot.RobotArg.html"]], "html() (boxes.servos.servoarg method)": [[13, "boxes.servos.ServoArg.html"]], "http_headers (boxes.formats.formats attribute)": [[13, "boxes.formats.Formats.http_headers"]], "inkbool() (in module boxes.gears)": [[13, "boxes.gears.inkbool"]], "inverse (boxes.edges.groovededge attribute)": [[13, "boxes.edges.GroovedEdge.inverse"]], "inverse (boxes.edges.groovededgecounterpart attribute)": [[13, "boxes.edges.GroovedEdgeCounterPart.inverse"]], "involute_intersect_angle() (in module boxes.gears)": [[13, "boxes.gears.involute_intersect_angle"]], "inx() (boxes.argparseedgetype method)": [[13, "boxes.ArgparseEdgeType.inx"]], "is_inverse() (boxes.edges.groovededgebase method)": [[13, "boxes.edges.GroovedEdgeBase.is_inverse"]], "kerf() (in module boxes.vectors)": [[13, "boxes.vectors.kerf"]], "length (boxes.servos.servo9g attribute)": [[13, "boxes.servos.Servo9g.length"]], "linspace() (in module boxes.gears)": [[13, "boxes.gears.linspace"]], "margin() (boxes.edges.chesthinge method)": [[13, "boxes.edges.ChestHinge.margin"]], "margin() (boxes.edges.chesthingepin method)": [[13, "boxes.edges.ChestHingePin.margin"]], "margin() (boxes.edges.chesthingetop method)": [[13, "boxes.edges.ChestHingeTop.margin"]], "margin() (boxes.edges.clickconnector method)": [[13, "boxes.edges.ClickConnector.margin"]], "margin() (boxes.edges.clickedge method)": [[13, "boxes.edges.ClickEdge.margin"]], "margin() (boxes.edges.compoundedge method)": [[13, "boxes.edges.CompoundEdge.margin"]], "margin() (boxes.edges.dovetailjoint method)": [[13, "boxes.edges.DoveTailJoint.margin"]], "margin() (boxes.edges.dovetailjointcounterpart method)": [[13, "boxes.edges.DoveTailJointCounterPart.margin"]], "margin() (boxes.edges.fingerjointedge method)": [[13, "boxes.edges.FingerJointEdge.margin"]], "margin() (boxes.edges.grippingedge method)": [[13, "boxes.edges.GrippingEdge.margin"]], "margin() (boxes.edges.handleedge method)": [[13, "boxes.edges.HandleEdge.margin"]], "margin() (boxes.edges.handleholeedge method)": [[13, "boxes.edges.HandleHoleEdge.margin"]], "margin() (boxes.edges.hinge method)": [[13, "boxes.edges.Hinge.margin"]], "margin() (boxes.edges.hingepin method)": [[13, "boxes.edges.HingePin.margin"]], "margin() (boxes.edges.lidright method)": [[13, "boxes.edges.LidRight.margin"]], "margin() (boxes.edges.lidsideright method)": [[13, "boxes.edges.LidSideRight.margin"]], "margin() (boxes.edges.mountingedge method)": [[13, "boxes.edges.MountingEdge.margin"]], "margin() (boxes.edges.rackedge method)": [[13, "boxes.edges.RackEdge.margin"]], "margin() (boxes.edges.roundedtriangleedge method)": [[13, "boxes.edges.RoundedTriangleEdge.margin"]], "margin() (boxes.edges.slottededge method)": [[13, "boxes.edges.SlottedEdge.margin"]], "margin() (boxes.edges.stackablebaseedge method)": [[13, "boxes.edges.StackableBaseEdge.margin"]], "margin() (boxes.servos.eyeedge method)": [[13, "boxes.servos.EyeEdge.margin"]], "mirrorx() (boxes.boxes method)": [[13, "boxes.Boxes.mirrorX"]], "mirrory() (boxes.boxes method)": [[13, "boxes.Boxes.mirrorY"]], "mirrorx() (in module boxes.pulley)": [[13, "boxes.pulley.mirrorx"]], "mmul() (in module boxes.vectors)": [[13, "boxes.vectors.mmul"]], "mountinghole() (boxes.boxes method)": [[13, "boxes.Boxes.mountingHole"]], "names (boxes.argparseedgetype attribute)": [[13, "boxes.ArgparseEdgeType.names"]], "nema_sizes (boxes.boxes attribute)": [[13, "boxes.Boxes.nema_sizes"]], "normalize() (in module boxes.vectors)": [[13, "boxes.vectors.normalize"]], "numfingers() (boxes.edges.boltpolicy method)": [[13, "boxes.edges.BoltPolicy.numFingers"]], "numfingers() (boxes.edges.bolts method)": [[13, "boxes.edges.Bolts.numFingers"]], "outset() (boxes.edges.hinge method)": [[13, "boxes.edges.Hinge.outset"]], "outset() (boxes.edges.hingepin method)": [[13, "boxes.edges.HingePin.outset"]], "outsetlen() (boxes.edges.hinge method)": [[13, "boxes.edges.Hinge.outsetlen"]], "outsetlen() (boxes.edges.hingepin method)": [[13, "boxes.edges.HingePin.outsetlen"]], "parts() (boxes.edges.cabinethingeedge method)": [[13, "boxes.edges.CabinetHingeEdge.parts"]], "pinheight() (boxes.edges.chesthingesettings method)": [[13, "boxes.edges.ChestHingeSettings.pinheight"]], "point_on_circle() (in module boxes.gears)": [[13, "boxes.gears.point_on_circle"]], "polygonwalls() (boxes.boxes method)": [[13, "boxes.Boxes.polygonWalls"]], "positive (boxes.edges.dovetailjoint attribute)": [[13, "boxes.edges.DoveTailJoint.positive"]], "positive (boxes.edges.dovetailjointcounterpart attribute)": [[13, "boxes.edges.DoveTailJointCounterPart.positive"]], "positive (boxes.edges.edge attribute)": [[13, "boxes.edges.Edge.positive"]], "positive (boxes.edges.fingerjointedge attribute)": [[13, "boxes.edges.FingerJointEdge.positive"]], "positive (boxes.edges.fingerjointedgecounterpart attribute)": [[13, "boxes.edges.FingerJointEdgeCounterPart.positive"]], "positive (boxes.edges.outsetedge attribute)": [[13, "boxes.edges.OutSetEdge.positive"]], "profile_data (boxes.pulley.pulley attribute)": [[13, "boxes.pulley.Pulley.profile_data"]], "pstoedit_candidates (boxes.formats.formats attribute)": [[13, "boxes.formats.Formats.pstoedit_candidates"]], "regularpolygon() (boxes.boxes method)": [[13, "boxes.Boxes.regularPolygon"]], "regularpolygonat() (boxes.boxes method)": [[13, "boxes.Boxes.regularPolygonAt"]], "regularpolygonhole() (boxes.boxes method)": [[13, "boxes.Boxes.regularPolygonHole"]], "relative_params (boxes.hexholessettings attribute)": [[13, "boxes.HexHolesSettings.relative_params"]], "relative_params (boxes.edges.cabinethingesettings attribute)": [[13, "boxes.edges.CabinetHingeSettings.relative_params"]], "relative_params (boxes.edges.chesthingesettings attribute)": [[13, "boxes.edges.ChestHingeSettings.relative_params"]], "relative_params (boxes.edges.clicksettings attribute)": [[13, "boxes.edges.ClickSettings.relative_params"]], "relative_params (boxes.edges.dovetailsettings attribute)": [[13, "boxes.edges.DoveTailSettings.relative_params"]], "relative_params (boxes.edges.fingerjointsettings attribute)": [[13, "boxes.edges.FingerJointSettings.relative_params"]], "relative_params (boxes.edges.flexsettings attribute)": [[13, "boxes.edges.FlexSettings.relative_params"]], "relative_params (boxes.edges.gearsettings attribute)": [[13, "boxes.edges.GearSettings.relative_params"]], "relative_params (boxes.edges.gripsettings attribute)": [[13, "boxes.edges.GripSettings.relative_params"]], "relative_params (boxes.edges.handleedgesettings attribute)": [[13, "boxes.edges.HandleEdgeSettings.relative_params"]], "relative_params (boxes.edges.hingesettings attribute)": [[13, "boxes.edges.HingeSettings.relative_params"]], "relative_params (boxes.edges.lidsettings attribute)": [[13, "boxes.edges.LidSettings.relative_params"]], "relative_params (boxes.edges.roundedtriangleedgesettings attribute)": [[13, "boxes.edges.RoundedTriangleEdgeSettings.relative_params"]], "relative_params (boxes.edges.settings attribute)": [[13, "boxes.edges.Settings.relative_params"]], "relative_params (boxes.edges.stackablesettings attribute)": [[13, "boxes.edges.StackableSettings.relative_params"]], "restore() (in module boxes)": [[13, "boxes.restore"]], "rightside (boxes.edges.lidleft attribute)": [[13, "boxes.edges.LidLeft.rightside"]], "rightside (boxes.edges.lidright attribute)": [[13, "boxes.edges.LidRight.rightside"]], "rightside (boxes.edges.lidsideleft attribute)": [[13, "boxes.edges.LidSideLeft.rightside"]], "rightside (boxes.edges.lidsideright attribute)": [[13, "boxes.edges.LidSideRight.rightside"]], "rotm() (in module boxes.vectors)": [[13, "boxes.vectors.rotm"]], "servo_axle (boxes.servos.servo9g attribute)": [[13, "boxes.servos.Servo9g.servo_axle"]], "set_font() (boxes.boxes method)": [[13, "boxes.Boxes.set_font"]], "set_source_color() (boxes.boxes method)": [[13, "boxes.Boxes.set_source_color"]], "showborderpoly() (boxes.boxes method)": [[13, "boxes.Boxes.showBorderPoly"]], "sizes (boxes.nuthole attribute)": [[13, "boxes.NutHole.sizes"]], "sizes() (boxes.gears.gears method)": [[13, "boxes.gears.Gears.sizes"]], "spacing (boxes.pulley.pulley attribute)": [[13, "boxes.pulley.Pulley.spacing"]], "startwidth() (boxes.edges.cabinethingeedge method)": [[13, "boxes.edges.CabinetHingeEdge.startwidth"]], "startwidth() (boxes.edges.chesthinge method)": [[13, "boxes.edges.ChestHinge.startwidth"]], "startwidth() (boxes.edges.chesthingefront method)": [[13, "boxes.edges.ChestHingeFront.startwidth"]], "startwidth() (boxes.edges.chesthingetop method)": [[13, "boxes.edges.ChestHingeTop.startwidth"]], "startwidth() (boxes.edges.clickedge method)": [[13, "boxes.edges.ClickEdge.startwidth"]], "startwidth() (boxes.edges.compoundedge method)": [[13, "boxes.edges.CompoundEdge.startwidth"]], "startwidth() (boxes.edges.fingerholeedge method)": [[13, "boxes.edges.FingerHoleEdge.startwidth"]], "startwidth() (boxes.edges.fingerjointedge method)": [[13, "boxes.edges.FingerJointEdge.startwidth"]], "startwidth() (boxes.edges.hingepin method)": [[13, "boxes.edges.HingePin.startwidth"]], "startwidth() (boxes.edges.lidright method)": [[13, "boxes.edges.LidRight.startwidth"]], "startwidth() (boxes.edges.lidsideright method)": [[13, "boxes.edges.LidSideRight.startwidth"]], "startwidth() (boxes.edges.mountingedge method)": [[13, "boxes.edges.MountingEdge.startwidth"]], "startwidth() (boxes.edges.outsetedge method)": [[13, "boxes.edges.OutSetEdge.startwidth"]], "startwidth() (boxes.edges.roundedtrianglefingerholesedge method)": [[13, "boxes.edges.RoundedTriangleFingerHolesEdge.startwidth"]], "startwidth() (boxes.edges.slottededge method)": [[13, "boxes.edges.SlottedEdge.startwidth"]], "startwidth() (boxes.edges.stackablebaseedge method)": [[13, "boxes.edges.StackableBaseEdge.startwidth"]], "startwidth() (boxes.edges.stackableholeedgetop method)": [[13, "boxes.edges.StackableHoleEdgeTop.startwidth"]], "startwidth() (boxes.servos.eyeedge method)": [[13, "boxes.servos.EyeEdge.startwidth"]], "step() (boxes.boxes method)": [[13, "boxes.Boxes.step"]], "surroundingwallpiece() (boxes.boxes method)": [[13, "boxes.Boxes.surroundingWallPiece"]], "svgmerge() (in module boxes.svgutil)": [[13, "boxes.svgutil.svgMerge"]], "tangent() (in module boxes.vectors)": [[13, "boxes.vectors.tangent"]], "teeth (boxes.pulley.pulley attribute)": [[13, "boxes.pulley.Pulley.teeth"]], "tickspermm() (in module boxes.svgutil)": [[13, "boxes.svgutil.ticksPerMM"]], "tooth_spaceing_curvefit() (in module boxes.pulley)": [[13, "boxes.pulley.tooth_spaceing_curvefit"]], "tooth_spacing() (in module boxes.pulley)": [[13, "boxes.pulley.tooth_spacing"]], "top() (boxes.servos.servo9g method)": [[13, "boxes.servos.Servo9g.top"]], "top() (boxes.servos.servo9gt method)": [[13, "boxes.servos.Servo9gt.top"]], "trapezoidsidewall() (boxes.boxes method)": [[13, "boxes.Boxes.trapezoidSideWall"]], "trapezoidwall() (boxes.boxes method)": [[13, "boxes.Boxes.trapezoidWall"]], "tx_sizes (boxes.boxes attribute)": [[13, "boxes.Boxes.tx_sizes"]], "types (boxes.gears.optionparser attribute)": [[13, "boxes.gears.OptionParser.types"]], "ui_group (boxes.boxes attribute)": [[13, "boxes.Boxes.ui_group"]], "undercut_max_k() (in module boxes.gears)": [[13, "boxes.gears.undercut_max_k"]], "undercut_min_angle() (in module boxes.gears)": [[13, "boxes.gears.undercut_min_angle"]], "undercut_min_teeth() (in module boxes.gears)": [[13, "boxes.gears.undercut_min_teeth"]], "vadd() (in module boxes.vectors)": [[13, "boxes.vectors.vadd"]], "vclip() (in module boxes.vectors)": [[13, "boxes.vectors.vclip"]], "vdiff() (in module boxes.vectors)": [[13, "boxes.vectors.vdiff"]], "vlength() (in module boxes.vectors)": [[13, "boxes.vectors.vlength"]], "vorthogonal() (in module boxes.vectors)": [[13, "boxes.vectors.vorthogonal"]], "vscalmul() (in module boxes.vectors)": [[13, "boxes.vectors.vscalmul"]], "vtransl() (in module boxes.vectors)": [[13, "boxes.vectors.vtransl"]], "wave() (boxes.edges.grippingedge method)": [[13, "boxes.edges.GrippingEdge.wave"]], "webinterface (boxes.boxes attribute)": [[13, "boxes.Boxes.webinterface"]], "width (boxes.servos.servo9g attribute)": [[13, "boxes.servos.Servo9g.width"]], "abox (class in boxes.generators.abox)": [[15, "boxes.generators.abox.ABox"]], "agricolainsert (class in boxes.generators.agricolainsert)": [[15, "boxes.generators.agricolainsert.AgricolaInsert"]], "alledges (class in boxes.generators.alledges)": [[15, "boxes.generators.alledges.AllEdges"]], "angledbox (class in boxes.generators.angledbox)": [[15, "boxes.generators.angledbox.AngledBox"]], "angledcutjig (class in boxes.generators.angledcutjig)": [[15, "boxes.generators.angledcutjig.AngledCutJig"]], "arcade (class in boxes.generators.arcade)": [[15, "boxes.generators.arcade.Arcade"]], "atreus21 (class in boxes.generators.atreus21)": [[15, "boxes.generators.atreus21.Atreus21"]], "basedbox (class in boxes.generators.basedbox)": [[15, "boxes.generators.basedbox.BasedBox"]], "bayonetbox (class in boxes.generators.bayonetbox)": [[15, "boxes.generators.bayonetbox.BayonetBox"]], "birdhouse (class in boxes.generators.birdhouse)": [[15, "boxes.generators.birdhouse.BirdHouse"]], "bottlestack (class in boxes.generators.bottlestack)": [[15, "boxes.generators.bottlestack.BottleStack"]], "bottletag (class in boxes.generators.bottletag)": [[15, "boxes.generators.bottletag.BottleTag"]], "breadbox (class in boxes.generators.breadbox)": [[15, "boxes.generators.breadbox.BreadBox"]], "burntest (class in boxes.generators.burntest)": [[15, "boxes.generators.burntest.BurnTest"]], "canstorage (class in boxes.generators.can_storage)": [[15, "boxes.generators.can_storage.CanStorage"]], "cardbox (class in boxes.generators.cardbox)": [[15, "boxes.generators.cardbox.CardBox"]], "cardholder (class in boxes.generators.cardholder)": [[15, "boxes.generators.cardholder.CardHolder"]], "castle (class in boxes.generators.castle)": [[15, "boxes.generators.castle.Castle"]], "closedbox (class in boxes.generators.closedbox)": [[15, "boxes.generators.closedbox.ClosedBox"]], "coffeecapsuleholder (class in boxes.generators.coffeecapsulesholder)": [[15, "boxes.generators.coffeecapsulesholder.CoffeeCapsuleHolder"]], "coindisplay (class in boxes.generators.coindisplay)": [[15, "boxes.generators.coindisplay.CoinDisplay"]], "concaveknob (class in boxes.generators.concaveknob)": [[15, "boxes.generators.concaveknob.ConcaveKnob"]], "console (class in boxes.generators.console)": [[15, "boxes.generators.console.Console"]], "console2 (class in boxes.generators.console2)": [[15, "boxes.generators.console2.Console2"]], "dicebox (class in boxes.generators.dicebox)": [[15, "boxes.generators.dicebox.DiceBox"]], "dinrailbox (class in boxes.generators.dinrailbox)": [[15, "boxes.generators.dinrailbox.DinRailBox"]], "discrack (class in boxes.generators.discrack)": [[15, "boxes.generators.discrack.DiscRack"]], "dispenser (class in boxes.generators.dispenser)": [[15, "boxes.generators.dispenser.Dispenser"]], "display (class in boxes.generators.display)": [[15, "boxes.generators.display.Display"]], "displaycase (class in boxes.generators.displaycase)": [[15, "boxes.generators.displaycase.DisplayCase"]], "dividertray (class in boxes.generators.dividertray)": [[15, "boxes.generators.dividertray.DividerTray"]], "doubleflexdoorbox (class in boxes.generators.doubleflexdoorbox)": [[15, "boxes.generators.doubleflexdoorbox.DoubleFlexDoorBox"]], "drillbox (class in boxes.generators.drillbox)": [[15, "boxes.generators.drillbox.DrillBox"]], "drillstand (class in boxes.generators.drillstand)": [[15, "boxes.generators.drillstand.DrillStand"]], "electronicsbox (class in boxes.generators.electronicsbox)": [[15, "boxes.generators.electronicsbox.ElectronicsBox"]], "eurorackskiff (class in boxes.generators.eurorackskiff)": [[15, "boxes.generators.eurorackskiff.EuroRackSkiff"]], "fanhole (class in boxes.generators.fanhole)": [[15, "boxes.generators.fanhole.FanHole"]], "filltest (class in boxes.generators.filltest)": [[15, "boxes.generators.filltest.FillTest"]], "flexbox (class in boxes.generators.flexbox)": [[15, "boxes.generators.flexbox.FlexBox"]], "flexbox2 (class in boxes.generators.flexbox2)": [[15, "boxes.generators.flexbox2.FlexBox2"]], "flexbox3 (class in boxes.generators.flexbox3)": [[15, "boxes.generators.flexbox3.FlexBox3"]], "flexbox4 (class in boxes.generators.flexbox4)": [[15, "boxes.generators.flexbox4.FlexBox4"]], "flexbox5 (class in boxes.generators.flexbox5)": [[15, "boxes.generators.flexbox5.FlexBox5"]], "flextest (class in boxes.generators.flextest)": [[15, "boxes.generators.flextest.FlexTest"]], "flextest2 (class in boxes.generators.flextest2)": [[15, "boxes.generators.flextest2.FlexTest2"]], "folder (class in boxes.generators.folder)": [[15, "boxes.generators.folder.Folder"]], "gearbox (class in boxes.generators.gearbox)": [[15, "boxes.generators.gearbox.GearBox"]], "gears (class in boxes.generators.gear)": [[15, "boxes.generators.gear.Gears"]], "halfbox (class in boxes.generators.halfbox)": [[15, "boxes.generators.halfbox.HalfBox"]], "heartbox (class in boxes.generators.heart)": [[15, "boxes.generators.heart.HeartBox"]], "hingebox (class in boxes.generators.hingebox)": [[15, "boxes.generators.hingebox.HingeBox"]], "holepattern (class in boxes.generators.holepattern)": [[15, "boxes.generators.holepattern.HolePattern"]], "hook (class in boxes.generators.hooks)": [[15, "boxes.generators.hooks.Hook"]], "integratedhingebox (class in boxes.generators.integratedhingebox)": [[15, "boxes.generators.integratedhingebox.IntegratedHingeBox"]], "jointpanel (class in boxes.generators.jointpanel)": [[15, "boxes.generators.jointpanel.JointPanel"]], "keypad (class in boxes.generators.keypad)": [[15, "boxes.generators.keypad.Keypad"]], "lbeam (class in boxes.generators.lbeam)": [[15, "boxes.generators.lbeam.LBeam"]], "laptopstand (class in boxes.generators.laptopstand)": [[15, "boxes.generators.laptopstand.LaptopStand"]], "laserclamp (class in boxes.generators.laserclamp)": [[15, "boxes.generators.laserclamp.LaserClamp"]], "laserholdfast (class in boxes.generators.laserholdfast)": [[15, "boxes.generators.laserholdfast.LaserHoldfast"]], "magazinfile (class in boxes.generators.magazinefile)": [[15, "boxes.generators.magazinefile.MagazinFile"]], "makitapowersupply (class in boxes.generators.makitapowersupply)": [[15, "boxes.generators.makitapowersupply.MakitaPowerSupply"]], "nemapattern (class in boxes.generators.nemapattern)": [[15, "boxes.generators.nemapattern.NemaPattern"]], "notesholder (class in boxes.generators.notesholder)": [[15, "boxes.generators.notesholder.NotesHolder"]], "openbox (class in boxes.generators.openbox)": [[15, "boxes.generators.openbox.OpenBox"]], "organpipe (class in boxes.generators.organpipe)": [[15, "boxes.generators.organpipe.OrganPipe"]], "ottobody (class in boxes.generators.ottobody)": [[15, "boxes.generators.ottobody.OttoBody"]], "ottolegs (class in boxes.generators.ottolegs)": [[15, "boxes.generators.ottolegs.OttoLegs"]], "ottosoles (class in boxes.generators.ottosoles)": [[15, "boxes.generators.ottosoles.OttoSoles"]], "paintstorage (class in boxes.generators.paintbox)": [[15, "boxes.generators.paintbox.PaintStorage"]], "paperbox (class in boxes.generators.paperbox)": [[15, "boxes.generators.paperbox.PaperBox"]], "phoneholder (class in boxes.generators.phoneholder)": [[15, "boxes.generators.phoneholder.PhoneHolder"]], "planetary (class in boxes.generators.planetary)": [[15, "boxes.generators.planetary.Planetary"]], "planetary2 (class in boxes.generators.planetary2)": [[15, "boxes.generators.planetary2.Planetary2"]], "platonic (class in boxes.generators.platonic)": [[15, "boxes.generators.platonic.Platonic"]], "polehook (class in boxes.generators.polehook)": [[15, "boxes.generators.polehook.PoleHook"]], "pulley (class in boxes.generators.pulley)": [[15, "boxes.generators.pulley.Pulley"]], "rack10box (class in boxes.generators.rack10box)": [[15, "boxes.generators.rack10box.Rack10Box"]], "rack19box (class in boxes.generators.rack19box)": [[15, "boxes.generators.rack19box.Rack19Box"]], "rack19halfwidth (class in boxes.generators.rack19halfwidth)": [[15, "boxes.generators.rack19halfwidth.Rack19HalfWidth"]], "rackbox (class in boxes.generators.rackbox)": [[15, "boxes.generators.rackbox.RackBox"]], "rectangularwall (class in boxes.generators.rectangularwall)": [[15, "boxes.generators.rectangularWall.RectangularWall"]], "regularbox (class in boxes.generators.regularbox)": [[15, "boxes.generators.regularbox.RegularBox"]], "regularstarbox (class in boxes.generators.regularstarbox)": [[15, "boxes.generators.regularstarbox.RegularStarBox"]], "robotarm (class in boxes.generators.robotarm)": [[15, "boxes.generators.robotarm.RobotArm"]], "rotary (class in boxes.generators.rotary)": [[15, "boxes.generators.rotary.Rotary"]], "roundedbox (class in boxes.generators.roundedbox)": [[15, "boxes.generators.roundedbox.RoundedBox"]], "royalgame (class in boxes.generators.royalgame)": [[15, "boxes.generators.royalgame.RoyalGame"]], "sbcmicrorack (class in boxes.generators.microrack)": [[15, "boxes.generators.microrack.SBCMicroRack"]], "shutterbox (class in boxes.generators.shutterbox)": [[15, "boxes.generators.shutterbox.ShutterBox"]], "sidedoorhousing (class in boxes.generators.sidedoorhousing)": [[15, "boxes.generators.sidedoorhousing.SideDoorHousing"]], "silverware (class in boxes.generators.silverwarebox)": [[15, "boxes.generators.silverwarebox.Silverware"]], "slidingdrawer (class in boxes.generators.slidingdrawer)": [[15, "boxes.generators.slidingdrawer.SlidingDrawer"]], "spicesrack (class in boxes.generators.spicesrack)": [[15, "boxes.generators.spicesrack.SpicesRack"]], "storagerack (class in boxes.generators.storagerack)": [[15, "boxes.generators.storagerack.StorageRack"]], "storageshelf (class in boxes.generators.storageshelf)": [[15, "boxes.generators.storageshelf.StorageShelf"]], "trafficlight (class in boxes.generators.trafficlight)": [[15, "boxes.generators.trafficlight.TrafficLight"]], "trayinsert (class in boxes.generators.trayinsert)": [[15, "boxes.generators.trayinsert.TrayInsert"]], "traylayout (class in boxes.generators.traylayout)": [[15, "boxes.generators.traylayout.TrayLayout"]], "traylayout2 (class in boxes.generators.traylayout)": [[15, "boxes.generators.traylayout.TrayLayout2"]], "trianglelamp (class in boxes.generators.trianglelamp)": [[15, "boxes.generators.trianglelamp.TriangleLamp"]], "twopiece (class in boxes.generators.two_piece)": [[15, "boxes.generators.two_piece.TwoPiece"]], "ubox (class in boxes.generators.ubox)": [[15, "boxes.generators.ubox.UBox"]], "unevenheightbox (class in boxes.generators.unevenheightbox)": [[15, "boxes.generators.unevenheightbox.UnevenHeightBox"]], "universalbox (class in boxes.generators.universalbox)": [[15, "boxes.generators.universalbox.UniversalBox"]], "waivyknob (class in boxes.generators.waivyknob)": [[15, "boxes.generators.waivyknob.WaivyKnob"]], "wallcaliper (class in boxes.generators.wallcaliperholder)": [[15, "boxes.generators.wallcaliperholder.WallCaliper"]], "wallchiselholder (class in boxes.generators.wallchiselholder)": [[15, "boxes.generators.wallchiselholder.WallChiselHolder"]], "wallconsole (class in boxes.generators.wallconsole)": [[15, "boxes.generators.wallconsole.WallConsole"]], "walldrillbox (class in boxes.generators.walldrillbox)": [[15, "boxes.generators.walldrillbox.WallDrillBox"]], "walledges (class in boxes.generators.walledges)": [[15, "boxes.generators.walledges.WallEdges"]], "wallpinrow (class in boxes.generators.wallpinrow)": [[15, "boxes.generators.wallpinrow.WallPinRow"]], "wallplaneholder (class in boxes.generators.wallplaneholder)": [[15, "boxes.generators.wallplaneholder.WallPlaneHolder"]], "wallpliersholder (class in boxes.generators.wallpliersholder)": [[15, "boxes.generators.wallpliersholder.WallPliersHolder"]], "wallslottedholder (class in boxes.generators.wallslottedholder)": [[15, "boxes.generators.wallslottedholder.WallSlottedHolder"]], "wallstairs (class in boxes.generators.wallstairs)": [[15, "boxes.generators.wallstairs.WallStairs"]], "walltypetray (class in boxes.generators.walltypetray)": [[15, "boxes.generators.walltypetray.WallTypeTray"]], "wallwrenchholder (class in boxes.generators.wallwrenchholder)": [[15, "boxes.generators.wallwrenchholder.WallWrenchHolder"]], "winerack (class in boxes.generators.winerack)": [[15, "boxes.generators.winerack.WineRack"]], "black": [[21, "term-Black"]], "blue": [[21, "term-Blue"]], "green": [[21, "term-Green"]], "red": [[21, "term-Red"]], "angle": [[21, "term-angle"]], "extra_length": [[21, "term-extra_length"]], "finger": [[21, "term-finger"]], "height": [[21, "term-height"]], "holedistance": [[21, "term-holedistance"]], "space": [[21, "term-space"]], "style": [[21, "term-style"]], "surroundingspaces": [[21, "term-surroundingspaces"]], "width": [[21, "term-width"]]}}) \ No newline at end of file diff --git a/html/usermanual.html b/html/usermanual.html new file mode 100644 index 0000000..21d2afd --- /dev/null +++ b/html/usermanual.html @@ -0,0 +1,352 @@ + + + + + + + + + Using Boxes.py — boxes.py 0.1 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Using Boxes.py

+
+
+

Boxes.py is made of a library that is not visible to the user and +multiple generators – each having its own set of parameters and +creating a drawing for it own type of object. These generators are +divided up into different groups to make it easier to find them:

+
    +
  • Boxes

  • +
  • Boxes with flex

  • +
  • Trays and Drawer Inserts

  • +
  • Shelves

  • +
  • Parts and Samples

  • +
  • Misc

  • +
  • Unstable

  • +
+

The parameters for each generators also come in groups.

+
+

Units of measurements

+

In general all measurements are in Millimeters (mm). There is no +option to change the units of measurement and there is no plan to add +such a option.

+

A second way to define lengths is as multiple of the material +thickness which is one of the standard parameters described +below. This allows features to retain their proportions even if some +parts depend on the material thickness.

+

The description texts should state the unit of each argument - +please open a ticket if the units are missing somewhere.

+
+
+

Default arguments

+

In the web interface this is the bottom group right before the +Render button. These are basically all technical settings that +have little to do with the object being rendered but more with the +material used and the way the drawing and the material is processed.

+

The settings are

+
+

thickness

+

The thickness of the material used. This value is used at many places +to define the sizes of features like finger joints, hinges, … It is +very important to get the value right - especially if there are +fingers that need to fit into some holes. Be aware that many materials +may differ from their nominal value. You should always measure the +thickness for every sheet unless you have a very reliable supply +that is known to stick very closely to specifications. For (ply) wood +even a 100th of a millimeter makes a notable difference in how stiff +the fit is. Harder more brittle materials may be even more picky.

+
+
+

burn

+

The burn correction aka kerf is the distance the laser has to keep +from the edge of the parts. If the laser would cut right on the edge +it would cut away the outside perimeter of the part. So the burn value is +basically the radius of the laser - or half the width of the laser cut.

+

The value of the burn parameter depends on your laser cutter, the +material cut and the thickness of the material. In addition it depends +on whether you want the parts to be over or under sized. Materials +that are spongy like wood can be cut oversized (larger burn value) so +they can be press fitted with some force and may be assembled without +glue. Brittle materials (like Acrylic) need to be cut undersized to +leave a gap for the glue.

+

Note: The way the burn param works is a bit counter intuitive. Bigger +burn values make a tighter fit. Smaller values make a looser fit.

+

Small changes in the burn param can make a notable difference. Typical +steps for adjustment are 0.01 or even 0.005mm to choose between +different amounts of force needed to press plywood together.

+

To find the right burn value cut out a rectangle and then measure how +much smaller it is than its nominal size. The burn value should be +around half of the difference. To test the fit for several values at +once you can use the BurnTest generator in the “Parts and Samples” section.

+
+
+

format

+

Boxes.py is able to create multiple formats. For most of them it +requires ps2edit. Without ps2edit only SVG +and postscript (ps) is supported. Otherwise you can also +select

+
    +
  • ai

  • +
  • dxf

  • +
  • gcode

  • +
  • pdf

  • +
  • plt

  • +
+

Other formats supported by ps2edit can be added easily. Please +open a ticket on GitHub if you need one.

+
+
+

tabs

+

Tabs are small bridges between the parts and surrounding material that +keep the part from falling out. In theory their width should be +affected by the burn parameter. But it is more practical to have both +independent so you can tune them separately. Most parts and generators +support this features but there may be some that don’t.

+

For plywood values of 0.2 to 0.3mm still allow getting the parts out +by hand (Depending on you laser cutter and the exact material). With +little more you will need a knife to cut them loose.

+
+
+

inner_corners

+

How to handle inner corners. Inner corners are an issue as a round +tool like a laser or mill cannot create sharp inner corners. There are +different options:

+
    +
  • loop create a loop that fills the corner

  • +
  • corner just a simple sharp corner in the path that will leave a +radius untouched.

  • +
  • backarc naive implementation with inverted arcs connection the +straight lines.

  • +
+

See also burn correction details

+
+
+

debug

+

Most regular users won’t need this option.

+

It adds some construction lines that are helpful for +developing new generators. Only few pieces actually support the +parameter. The most notable being finger holes that show the border of +the piece they belong to. This helps checking whether the finger holes +are placed correctly.

+
+
+

reference

+

Converting vector graphics is error prone. Many formats have very +weird ideas how their internal units translates to real world +dimensions. If reference is set to non zero Boxes.py renders a rectangle of +the given length. It can be used to check if the drawing is still at +the right scale or may give clues on how to scale it back to the right +proportions.

+
+
+
+

Common Parameters and Types

+
+

Section parameters

+

Some generators support an arbitrary number of sections. This can be used for rows or columns of compartments, staggered heights or otherwise dividing some length in multiple sub sections. The standard parameter making use of this are sx, sy and sh (instead of x, y and h).

+

Most generators will add walls between the comparments, so the total size might be larger depending on the number of compartments (and additional walls).

+

The sizes of the sections are divided by a colon (:) e.g. 30:25.5:70. Instead of repeating the same value they can be replaced by value*numberofsections e.g. 50*3 meaning the same as 50:50:50. To equally divide a length into several sections overallwidth/numberofsections can be used - e.g. 120/4 being the same as 30:30:30:30. All these formats can be freely mixed.

+
+
+

mounting_holes

+

Some generators provide the option to create pear shaped mounting holes. To generate the right size holes, the shaft and the head diameter of the mounting screw must be configured. The format is “shaft:head”, both diameters given in mm (e.g 3.5:6.5). If only the shaft diameter is given (e.g. 3.5), a round mounting hole is generated. Setting the mounting hole diameter parameter to 0 disables the creation of mounting holes.

+
+
+

outside

+

Most measurements are internal sizes. If a generator offers this parameter it will re-calculate the inner sizes to fit walls and outside features within the given dimensions. This can be a bit surprising for edge types that have protrusions like hinge eyes, handles, feet, etc as those are typically also taken into account. If the dimensions are not sufficient to accommodate these features the box may not work properly. Most generators do not have checks for such issues (like negative height) and it is left in the responsibility of the user to check if the result still is sane.

+

For generators offering multiple compartments this will also fit-in the inner walls. It will sum up all sections then subtract the space needed for the walls and then scale all compartments so they will fill the remaining space.

+
+
+
+

Edge Type parameters

+

All but the simplest edge types have a number of settings controlling +how exactly they should look. Generators are encouraged to offer these +settings to the user. In the web interface they are folded up. In the +command line interface they are grouped together. Users should be +aware that not all settings are practical to change. For now Boxes.py +does not allow hiding some settings.

+
+

Finger Joint Settings

+
+
finger

width of the fingers in multiples of the thickness

+
+
space

width of the spaces between fingers in multiples of the thickness

+
+
surroundingspaces

amount of space before the first and after the last finger. This is in multiples of regular space between fingers. The actual space is larger when needed but can be smaller for very short edges.

+
+
style

how finger joints should look like. There may be more styles to choose from in the future. Note that snap fingers will only be drawn for fingers of width 1.9 and above.

+
+
extra_length

Make the outset part of the finger joint longer to allow grinding off burn marks. Note that this may not be great for non 90° joints where the corner is butted against the opposing cutout.

+
+
+
+
+

Stackable Edge Settings

+

For boxes to actually stack they need to be the same width and depth and angle, width and height of the feet need to be the same.

+
+
angle

inside angle of the feet.

+
+
height

height of the feet

+
+
holedistance

distance from finger holes to bottom edge. May be reduced to save height by sacrificing stability of the connection to the bottom of the box.

+
+
width

width of the feet

+
+
+
+
+
+

Colors

+

The generated files uses the following color conventions:

+
+
Black

The outer edges of a part

+
+
Blue

Inner edges of a part

+
+
Red

Comments or help lines that are not meant to be cut or etched

+
+
Green

Etchings

+
+
+

Normally you will cut things in the order: Green, Blue, Black. If other +colors are present, the meaning should hopefully be obvious.

+
+
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..f11ef23 --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ + + + + + + + Page Redirection + + + If you are not redirected automatically, follow this link. + +