diff --git a/build123d_common.py b/build123d_common.py index 8167656..ab22e0e 100644 --- a/build123d_common.py +++ b/build123d_common.py @@ -1,23 +1,39 @@ """ +Build123D Common + +name: build123d_common.py +by: Gumyr +date: July 12th 2022 + +desc: + This python module is a library used to build 3D parts. TODO: - Update Vector so it can be initialized with a Vertex or Location - Update VectorLike to include a Vertex and Location + +license: + + Copyright 2022 Gumyr + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + """ from math import radians from typing import Union from enum import Enum, auto -from cadquery import ( - Edge, - Wire, - Vector, - Shape, - Location, - Compound, - Solid, -) -from cadquery.occ_impl.shapes import VectorLike, Real -from OCP.gp import gp_Vec, gp_Pnt, gp_Ax1, gp_Dir, gp_Trsf +from cadquery import Edge, Wire, Vector, Location +from OCP.gp import gp_Pnt, gp_Ax1, gp_Dir, gp_Trsf import cq_warehouse.extensions @@ -61,10 +77,6 @@ class Keep(Enum): BOTH = auto() -class FilterBy(Enum): - LAST_OPERATION = auto() - - class Mode(Enum): """Combination Mode""" @@ -116,17 +128,6 @@ class Axis(Enum): Z = auto() -class CqObject(Enum): - EDGE = auto() - FACE = auto() - VERTEX = auto() - - -class BuildAssembly: - def add(self): - pass - - class SortBy(Enum): X = auto() Y = auto() diff --git a/build_line.py b/build_line.py index 679401c..67dcc89 100644 --- a/build_line.py +++ b/build_line.py @@ -527,7 +527,7 @@ class ThreePointArc(Edge): Add an arc generated by three points. Args: - pts (VectorLike): sequence of three points + pts (VectorLike): sequence of three points mode (Mode, optional): combination mode. Defaults to Mode.ADDITION. Raises: diff --git a/build_part.py b/build_part.py index d8329d0..01fec7a 100644 --- a/build_part.py +++ b/build_part.py @@ -11,6 +11,8 @@ desc: TODO: - add TwistExtrude, ProjectText - add centered to wedge +- add Mode.REPLACE for operations like fillet that change the part +- add a Workplane class with a Plane input license: diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/advantages.rst b/docs/advantages.rst new file mode 100644 index 0000000..7304ba8 --- /dev/null +++ b/docs/advantages.rst @@ -0,0 +1,124 @@ +########## +Advantages +########## + +As mentioned previously, the most significant advantage is that Build123D is more pythonic. +Specifically: + +******************************* +Standard Python Context Manager +******************************* +The creation of standard instance variables, looping and other normal python operations +is enabled by the replacement of method chaining (fluent programming) with a standard +python context manager. + +.. code-block:: python + + # CadQuery Fluent API + pillow_block = (cq.Workplane("XY") + .box(height, width, thickness) + .edges("|Z") + .fillet(fillet) + .faces(">Z") + .workplane() + ... + ) + +.. code-block:: python + + # Build123D API + with BuildPart() as pillow_block: + with BuildSketch() as plan: + Rectangle(width, height) + FilletSketch(*plan.vertices(), radius=fillet) + Extrude(thickness) + ... + +The use of the standard `with` block allows standard python instructions to be +inserted anyway in the code flow. One can insert a CQ-editor `debug` or standard `print` +statement anywhere in the code without impacting functionality. Simple python +`for` loops can be used to repetitively create objects instead of forcing users +into using more complex `lambda` and `iter` operations. + +******************** +Instantiated Objects +******************** +Each object and operation is now a class instantiation that interacts with the +active context implicitly for the user. These instantiations can be assigned to +an instance variable as with standard python programming for direct use. + +.. code-block:: python + + with BuildSketch() as plan: + r = Rectangle(width, height) + print(r.Area()) + ... + +********* +Operators +********* +New operators have been created to extract information from objects created previously +in the code. The `@` operator extracts the position along an Edge or Wire while the +`%` operator extracts the tangent along an Edge or Wire. The position parameter are float +values between 0.0 and 1.0 which represent the beginning and end of the line. In the following +example, a spline is created from the end of l5 (`l5 @ 1`) to the beginning of l6 (`l6 @ 0`) +with tangents equal to the tangents of l5 and l6 at their end and beginning respectively. +Being able to extract information from existing features allows the user to "snap" new +features to these points without knowing their numeric values. + +.. code-block:: python + + with BuildLine() as outline: + ... + l5 = Polyline(...) + l6 = Polyline(...) + Spline(l5 @ 1, l6 @ 0, tangents=(l5 % 1, l6 % 0)) + + +********************** +Last Operation Objects +********************** +All of the `vertices()`, `edges()`, `faces()`, and `solids()` methods of the builders +can either return all of the objects requested or just the objects changed during the +last operation. This allows the user to easily access features for further refinement, +as shown in the following code where the final line selects the edges that were added +by the last operation and fillets them. Such a selection would be quite difficult +otherwise. + +.. literalinclude:: ../examples/intersecting_pipes.py + + +********** +Extensions +********** +Extending Build123D is relatively simple in that custom objects or operations +can be created as new classes without the need to monkey patch any of the +core functionality. These new classes will be seen in IDEs which is not +possible with monkey patching the core CadQuery classes. + +***** +Enums +***** +All `Literal` strings have been replaced with `Enum` which allows IDEs to +prompt users for valid options without having to refer to documentation. + +*************************** +Selectors replaced by Lists +*************************** +Sting based selectors have been replaced with standard python filters and +sorting which opens up the fully functionality of python list functionality. +To aid the user, common operations have been optimized as shown here along with +a fully custom selection: + +.. code-block:: python + + top = rail.faces().filter_by_normal(Axis.Z)[-1] + ... + outside_vertices = filter( + lambda v: (v.Y == 0.0 or v.Y == height) and -width / 2 < v.X < width / 2, + din.vertices(), + ) + +One can sort by all of the following attributes: + +.. autoclass:: build123d_common.SortBy diff --git a/docs/build_line.rst b/docs/build_line.rst new file mode 100644 index 0000000..45a2b4b --- /dev/null +++ b/docs/build_line.rst @@ -0,0 +1,26 @@ +######### +BuildLine +######### +.. py:module:: build_line + +.. autoclass:: BuildLine + :members: + +======= +Objects +======= +.. autoclass:: CenterArc +.. autoclass:: Helix +.. autoclass:: Line +.. autoclass:: PolarLine +.. autoclass:: Polyline +.. autoclass:: RadiusArc +.. autoclass:: SagittaArc +.. autoclass:: Spline +.. autoclass:: TangentArc +.. autoclass:: ThreePointArc + +========== +Operations +========== +.. autoclass:: MirrorToLine diff --git a/docs/build_part.rst b/docs/build_part.rst new file mode 100644 index 0000000..084af32 --- /dev/null +++ b/docs/build_part.rst @@ -0,0 +1,39 @@ +######### +BuildPart +######### + +.. py:module:: build_part + +.. autoclass:: BuildPart + :members: + +======= +Objects +======= +.. autoclass:: AddToPart +.. autoclass:: Box +.. autoclass:: Cone +.. autoclass:: Cylinder +.. autoclass:: Sphere +.. autoclass:: Torus +.. autoclass:: Wedge + +========== +Operations +========== +.. autoclass:: ChamferPart +.. autoclass:: CounterBoreHole +.. autoclass:: CounterSinkHole +.. autoclass:: Extrude +.. autoclass:: FilletPart +.. autoclass:: Hole +.. autoclass:: Loft +.. autoclass:: PolarArrayToPart +.. autoclass:: PushPointsToPart +.. autoclass:: RectangularArrayToPart +.. autoclass:: Revolve +.. autoclass:: Shell +.. autoclass:: Split +.. autoclass:: Sweep +.. autoclass:: WorkplanesFromFaces + diff --git a/docs/build_sketch.rst b/docs/build_sketch.rst new file mode 100644 index 0000000..4433640 --- /dev/null +++ b/docs/build_sketch.rst @@ -0,0 +1,39 @@ +########### +BuildSketch +########### + +.. py:module:: build_sketch + +.. autoclass:: BuildSketch + :members: + +======= +Objects +======= +.. autoclass:: AddToSketch +.. autoclass:: Circle +.. autoclass:: Ellipse +.. autoclass:: Polygon +.. autoclass:: Rectangle +.. autoclass:: RegularPolygon +.. autoclass:: SlotArc +.. autoclass:: SlotCenterPoint +.. autoclass:: SlotCenterToCenter +.. autoclass:: SlotOverall +.. autoclass:: Text +.. autoclass:: Trapezoid + +========== +Operations +========== +.. autoclass:: BoundingBoxSketch +.. autoclass:: BuildFace +.. autoclass:: BuildHull +.. autoclass:: ChamferSketch +.. autoclass:: FilletSketch +.. autoclass:: MirrorToSketch +.. autoclass:: Offset +.. autoclass:: PolarArrayToSketch +.. autoclass:: PushPointsToSketch +.. autoclass:: RectangularArrayToSketch + diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..9ac0fd9 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,93 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys + +build123d_path = os.path.dirname(os.path.abspath(os.getcwd())) +source_files_path = os.path.join(build123d_path, "../") +sys.path.insert(0, source_files_path) +sys.path.append(os.path.abspath("sphinxext")) +sys.path.insert(0, os.path.abspath(".")) +sys.path.insert(0, os.path.abspath("../")) + +# -- Project information ----------------------------------------------------- + +project = "Build123D" +copyright = "2022, Gumyr" +author = "Gumyr" + +# The full version, including alpha/beta/rc tags +with open(os.path.join(build123d_path, "setup.cfg")) as f: + setup_cfg = f.readlines() +for line in setup_cfg: + if "version =" in line: + release = line.split("=")[1].strip() + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.napoleon", + "sphinx.ext.autodoc", + "sphinx_autodoc_typehints", + "sphinx.ext.doctest", +] + +# Napoleon settings +napoleon_google_docstring = True +napoleon_numpy_docstring = True +napoleon_include_init_with_doc = False +napoleon_include_private_with_doc = False +napoleon_include_special_with_doc = False +napoleon_use_admonition_for_examples = False +napoleon_use_admonition_for_notes = False +napoleon_use_admonition_for_references = False +napoleon_use_ivar = True +napoleon_use_param = True +napoleon_use_rtype = True +napoleon_use_keyword = True +napoleon_custom_sections = None + + +autodoc_typehints = ["description"] +# autodoc_typehints = ["both"] +autodoc_mock_imports = ["cadquery", "pkg_resources", "OCP"] + +# Sphinx settings +add_module_names = False +python_use_unqualified_type_names = True + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +# html_theme = "alabaster" +html_theme = "sphinx_rtd_theme" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] diff --git a/docs/environment.yaml b/docs/environment.yaml new file mode 100644 index 0000000..351d8eb --- /dev/null +++ b/docs/environment.yaml @@ -0,0 +1,10 @@ +name: docs +channels: + - conda-forge + - defaults +dependencies: + - sphinx + - nbsphinx + - pip: + - sphinx_rtd_theme + - git+https://github.com/gumyr/cq_warehouse.git#egg=cq_warehouse \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..3e3899b --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,90 @@ +.. + Build123D readthedocs documentation + + by: Gumyr + date: July 13th 2022 + + desc: This is the documentation for Build123D on readthedocs + + license: + + Copyright 2022 Gumyr + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +.. highlight:: python + +Build123D is an alternate to the `CadQuery `_ +Fluent API. It has several advantages over this API but the largest one is that Build123D is more +Pythonic than CadQuery 2.x. + +######## +Overview +######## + +Build123D uses the standard python context manager - e.g. the `with` statement often used when +working with files - as a builder of the object under construction. Once the object is complete +it can be extracted from the builders and used in other ways: for example exported as a STEP +file or used in an Assembly. There are three builders available: + +* **BuildLine**: a builder of one dimensional objects - those with the property + of length but not of area or volume - typically used + to create complex lines used in sketches or paths. +* **BuildSketch**: a builder of planar two dimensional objects - those with the property + of area but not of volume - typically used to create 2D drawings that are extruded into 3D parts. +* **BuildPart**: a builder of three dimensional objects - those with the property of volume - + used to create individual parts. + +The three builders work together in a hierarchy as follows: + +.. code-block:: python + + with BuildPart() as my_part: + ... + with BuildSketch() as my_sketch: + ... + with BuildLine() as my_line: + ... + ... + ... + +where `my_line` will be added to `my_sketch` once the line is complete and `my_sketch` will be +added to `my_part` once the sketch is complete. + +As an example, consider the design of a simple bearing pillow block: + +.. literalinclude:: ../examples/pillow_block.py + + +################# +Table Of Contents +################# + +.. toctree:: + :maxdepth: 2 + + installation.rst + advantages.rst + key_concepts.rst + build_line.rst + build_sketch.rst + build_part.rst + +================== +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..e7ef434 --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,8 @@ +############ +Installation +############ +Install from github: + +.. doctest:: + + >>> python3 -m pip install git+https://github.com/gumyr/cq_warehouse.git#egg=cq_warehouse diff --git a/docs/key_concepts.rst b/docs/key_concepts.rst new file mode 100644 index 0000000..b41c5a9 --- /dev/null +++ b/docs/key_concepts.rst @@ -0,0 +1,191 @@ +############ +Key Concepts +############ + +The following key concepts will help new users understand Build123D quickly. + +******** +Builders +******** + +The three builders, `BuildLine`, `BuildSketch`, and `BuildPart` are tools to create +new objects - not the objects themselves. Each of the objects and operations applicable +to these builders create objects of the standard CadQuery Direct API, most commonly +`Compound` objects. This is opposed to CadQuery's Fluent API which creates objects +of the `Workplane` class which frequently needed to be converted back to base +class for further processing. + +One can access the objects created by these builders by referencing the appropriate +instance variable. For example: + +.. code-block:: python + + with BuildPart() as my_part: + ... + + show_object(my_part.part) + +.. code-block:: python + + with BuildSketch() as my_sketch: + ... + + show_object(my_sketch.sketch) + +.. code-block:: python + + with BuildLine() as my_line: + ... + + show_object(my_line.line) + +**************** +Operation Inputs +**************** + +When one is operating on an existing object, e.g. adding a fillet to a part, +a sequence of objects is often required. A python sequence of objects is +simply either a single object or a string of objects separated by commas. +To pass an array into a sequence, precede it with a `*` operator. +When a sequence is followed by another parameter, that parameter must be +entered as a keyword parameter (e.g. radius=1) to separate this parameter +from the preceding sequence. + +Here is the definition of `FilletPart` to help illustrate: + +.. code-block:: python + + class FilletPart(Compound): + def __init__(self, *edges: Edge, radius: float): + +To use this fillet operation, a sequence of edges must be provided followed by +a fillet radius as follows: + +.. code-block:: python + + with BuildPart() as pipes: + Box(10, 10, 10, rotation=(10, 20, 30)) + ... + FilletPart(*pipes.edges(Select.LAST), radius=0.2) + +Here the list of edges from the last operation of the `pipes` builder are converted +to a sequence and a radius is provided as a keyword argument. + +***************** +Combination Modes +***************** +Almost all objects or operations have a `mode` parameter which is defined by the +`Mode` Enum class as follows: + +.. code-block:: python + + class Mode(Enum): + ADDITION = auto() + SUBTRACTION = auto() + INTERSECTION = auto() + CONSTRUCTION = auto() + PRIVATE = auto() + +The `mode` parameter describes how the user would like the object or operation to +interact with the object within the builder. For example, `Mode.ADDITION` will +integrate a new object(s) in with an existing `part`. Note that a part doesn't +necessarily have to be a single object so multiple distinct objects could be added +resulting is multiple objects stored as a `Compound` object. As one might expect +`Mode.SUBTRACTION` and `Mode.INTERSECTION` subtract from or intersect and object +with the builder's object. `Mode.PRIVATE` instructs the builder that this object +should not be combined with the builder's object in any way. + +Most commonly, the default `mode` is `Mode.ADDITION` but this isn't always true. +For example, the `Hole` classes use a default `Mode.SUBTRACTION` as they remove +a volume from the part under normal circumstances. However, the `mode` used in +the `Hole` classes can be specified as `Mode.ADDITION` or `Mode.INTERSECTION` to +help in inspection or debugging. + +********************************* +Pushing Points & Rotating Objects +********************************* + +Build123D stores points (to be specific `Locations`) internally to be used as +positions for the placement of new objects. By default, a single location +will be created at the origin of the given workplane such that: + +.. code-block:: python + + with BuildPart() as pipes: + Box(10, 10, 10, rotation=(10, 20, 30)) + +will create a single 10x10x10 box centered at (0,0,0) - by default objects are +centered. One can create multiple objects by pushing points prior to creating +objects as follows: + +.. code-block:: python + + with BuildPart() as pipes: + PushPointsToPart((-10, -10, -10), (10, 10, 10)) + Box(10, 10, 10, rotation=(10, 20, 30)) + +which will create two boxes. Note that whenever points are pushed, previous +points are replaced. + +To orient a part, a `rotation` parameter is available on `BuildSketch`` and +`BuildPart` APIs. When working in a sketch, the rotation is a single angle in +degrees so the parameter is a float. When working on a part, the rotation is +a three dimensional `Rotation` object of the form +`Rotation(, , )` although a simple three tuple of +floats can be used as input. As 3D rotations are not cumulative, one can +combine rotations with the `*` operator like this: +`Rotation(10, 20, 30) * Rotation(0, 90, 0)` to generate any desired rotation. + +.. hint:: + Experts Only + + `PushPoints` will accept `Location` objects for input which allows one + to specify both the position and orientation. However, the orientation + is often determined by the `Plane` that an object was created on. + `Rotation` is a subclass of `Location` and therefore will also accept + a position component. + +************************* +Builder's Pending Objects +************************* + +When a builder exits, it will push the object created back to its parent if +there was one. Here is an example: + +.. code-block:: python + + with BuildPart() as pillow_block: + with BuildSketch() as plan: + Rectangle(width, height) + FilletSketch(*plan.vertices(), radius=fillet) + Extrude(thickness) + +`BuildSketch` exits after the `FilletSketch` operation and when doing so it transfers +the sketch to the `pillow_block` instance of `BuildPart` as the internal instance variable +`pending_faces`. This allows the `Extrude` operation to be immediately invoked as it +extrudes these pending faces into `Solid` objects. Likewise, `Loft` will take all of the +`pending_faces` and attempt to create a single `Solid` object from them. + +Normally the user will not need to interact directly with pending objects. + +************************************* +Multiple Work Planes - BuildPart Only +************************************* + +When `BuildPart` is invoked it will by default select the XY plane for the user to work on. +One can work on any plane by overriding this `workplane` parameter. The `workplane` can be changed +at any time to one or more planes which is most commonly used to create workplanes from +existing object Faces. The `WorkplanesFromFaces` class is used to do this as shown below: + +.. code-block:: python + + with BuildPart() as pipes: + Box(10, 10, 10, rotation=(10, 20, 30)) + WorkplanesFromFaces(*pipes.faces(), replace=True) + with BuildSketch() as pipe: + Circle(4) + Extrude(-5, mode=Mode.SUBTRACTION) + +In this example a `Box` is created and workplanes are created from each of the box's faces. +The following sketch is then created on each of these workplanes and the `Extrude` operation +creates holes in each of the faces of the box. diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..153be5e --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd