Spaces:
Sleeping
Sleeping
| .. _extending: | |
| Extending CadQuery | |
| ====================== | |
| If you find that CadQuery does not suit your needs, you can easily extend it. CadQuery provides several extension | |
| methods: | |
| * You can load plugins others have developed. This is by far the easiest way to access other code | |
| * You can define your own plugins. | |
| * You can use OCP scripting directly | |
| Using OpenCascade methods | |
| ------------------------- | |
| The easiest way to extend CadQuery is to simply use OpenCascade/OCP scripting inside of your build method. Just about | |
| any valid OCP script will execute just fine. For example, this simple CadQuery script:: | |
| return cq.Workplane("XY").box(1.0, 2.0, 3.0).val() | |
| is actually equivalent to:: | |
| from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox | |
| from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt | |
| return cq.Shape.cast( | |
| BRepPrimAPI_MakeBox( | |
| gp_Ax2(gp_Pnt(-0.5, -1.0, -1.5), gp_Dir(0, 0, 1)), 1.0, 2.0, 3.0 | |
| ).Shape() | |
| ) | |
| As long as you return a valid OCP Shape, you can use any OCP methods you like. You can even mix and match the | |
| two. For example, consider this script, which creates a OCP box, but then uses CadQuery to select its faces:: | |
| box1 = cq.Shape.cast( | |
| BRepPrimAPI_MakeBox( | |
| gp_Ax2(gp_Pnt(-0.5, -1.0, -1.5), gp_Dir(0, 0, 1)), 1.0, 2.0, 3.0 | |
| ).Shape() | |
| ) | |
| return box1.faces(">X").Area() # return 6.0 | |
| Extending CadQuery: Plugins | |
| ---------------------------- | |
| Though you can get a lot done with OpenCascade, the code gets pretty nasty in a hurry. CadQuery shields you from | |
| a lot of the complexity of the OpenCascade API. | |
| You can get the best of both worlds by wrapping your OCP script into a CadQuery plugin. | |
| A CadQuery plugin is simply a function that is attached to the CadQuery :py:meth:`cadquery.CQ` or :py:meth:`cadquery.Workplane` class. | |
| When connected, your plugin can be used in the chain just like the built-in functions. | |
| There are a few key concepts important to understand when building a plugin | |
| The Stack | |
| ------------------- | |
| Every CadQuery object has a local stack, which contains a list of items. The items on the stack will be | |
| one of these types: | |
| * **A CadQuery SolidReference object**, which holds a reference to a OCP solid | |
| * **A OCP object**, a Vertex, Edge, Wire, Face, Shell, Solid, or Compound | |
| The stack is available by using self.objects, and will always contain at least one object. | |
| .. note:: | |
| Objects and points on the stack are **always** in global coordinates. Similarly, any objects you | |
| create must be created in terms of global coordinates as well! | |
| Preserving the Chain | |
| ----------------------- | |
| CadQuery's fluent API relies on the ability to chain calls together one after another. For this to work, | |
| you must return a valid CadQuery object as a return value. If you choose not to return a CadQuery object, | |
| then your plugin will end the chain. Sometimes this is desired for example :py:meth:`cadquery.Workplane.size` | |
| There are two ways you can safely continue the chain: | |
| 1. **return self** If you simply wish to modify the stack contents, you can simply return a reference to | |
| self. This approach is destructive, because the contents of the stack are modified, but it is also the | |
| simplest. | |
| 2. :py:meth:`cadquery.Workplane.newObject` Most of the time, you will want to return a new object. Using newObject will | |
| return a new CQ or Workplane object having the stack you specify, and will link this object to the | |
| previous one. This preserves the original object and its stack. | |
| Helper Methods | |
| ----------------------- | |
| When you implement a CadQuery plugin, you are extending CadQuery's base objects. As a result, you can call any | |
| CadQuery or Workplane methods from inside of your extension. You can also call a number of internal methods that | |
| are designed to aid in plugin creation: | |
| * :py:meth:`cadquery.Workplane._makeWireAtPoints` will invoke a factory function you supply for all points on the stack, | |
| and return a properly constructed cadquery object. This function takes care of registering wires for you | |
| and everything like that | |
| * :py:meth:`cadquery.Workplane.newObject` returns a new Workplane object with the provided stack, and with its parent set | |
| to the current object. The preferred way to continue the chain | |
| * :py:meth:`cadquery.Workplane.findSolid` returns the first Solid found in the chain, working from the current object upwards | |
| in the chain. commonly used when your plugin will modify an existing solid, or needs to create objects and | |
| then combine them onto the 'main' part that is in progress | |
| * :py:meth:`cadquery.Workplane._addPendingWire` must be called if you add a wire. This allows the base class to track all the wires | |
| that are created, so that they can be managed when extrusion occurs. | |
| * :py:meth:`cadquery.Workplane.wire` gathers up all of the edges that have been drawn ( eg, by line, vline, etc ), and | |
| attempts to combine them into a single wire, which is returned. This should be used when your plugin creates | |
| 2D edges, and you know it is time to collect them into a single wire. | |
| * :py:meth:`cadquery.Workplane.plane` provides a reference to the workplane, which allows you to convert between workplane | |
| coordinates and global coordinates: | |
| * :py:meth:`cadquery.occ_impl.geom.Plane.toWorldCoords` will convert local coordinates to global ones | |
| * :py:meth:`cadquery.occ_impl.geom.Plane.toLocalCoords` will convert from global coordinates to local coordinates | |
| Coordinate Systems | |
| ----------------------- | |
| Keep in mind that the user may be using a work plane that has created a local coordinate system. Consequently, | |
| the orientation of shapes that you create are often implicitly defined by the user's workplane. | |
| Any objects that you create must be fully defined in *global coordinates*, even though some or all of the users' | |
| inputs may be defined in terms of local coordinates. | |
| Linking in your plugin | |
| ----------------------- | |
| Your plugin is a single method, which is attached to the main Workplane or CadQuery object. | |
| Your plugin method's first parameter should be 'self', which will provide a reference to base class functionality. | |
| You can also accept other arguments. | |
| To install it, simply attach it to the CadQuery or Workplane object, like this:: | |
| def _yourFunction(self, arg1, arg): | |
| # do stuff | |
| return whatever_you_want | |
| cq.Workplane.yourPlugin = _yourFunction | |
| That's it! | |
| CadQueryExample Plugins | |
| ----------------------- | |
| Some core cadquery code is intentionally written exactly like a plugin. | |
| If you are writing your own plugins, have a look at these methods for inspiration: | |
| * :py:meth:`cadquery.Workplane.polygon` | |
| * :py:meth:`cadquery.Workplane.cboreHole` | |
| Plugin Example | |
| ----------------------- | |
| This ultra simple plugin makes cubes of the specified size for each stack point. | |
| .. cadquery:: | |
| import cadquery as cq | |
| from cadquery.func import box | |
| def makeCubes(self, length): | |
| # self refers to the Workplane object | |
| # inner method that creates a cube | |
| def _singleCube(loc): | |
| # loc is a location in local coordinates | |
| # since we're using eachpoint with useLocalCoordinates=True | |
| return box(length, length, length).locate(loc) | |
| # use CQ utility method to iterate over the stack, call our | |
| # method, and convert to/from local coordinates. | |
| return self.eachpoint(_singleCube, True) | |
| # link the plugin into CadQuery | |
| cq.Workplane.makeCubes = makeCubes | |
| # use the plugin | |
| result = ( | |
| cq.Workplane("XY") | |
| .box(6.0, 8.0, 0.5) | |
| .faces(">Z") | |
| .rect(4.0, 4.0, forConstruction=True) | |
| .vertices() | |
| .makeCubes(1.0) | |
| .combine() | |
| ) | |
| Extending CadQuery: Special Methods | |
| ----------------------------------- | |
| The above-mentioned approach has one drawback, it requires monkey-patching or subclassing. To avoid this | |
| one can also use the following special methods of :py:class:`cadquery.Workplane` and :py:class:`cadquery.Sketch` | |
| and write plugins in a more functional style. | |
| * :py:meth:`cadquery.Workplane.map` | |
| * :py:meth:`cadquery.Workplane.apply` | |
| * :py:meth:`cadquery.Workplane.invoke` | |
| * :py:meth:`cadquery.Sketch.map` | |
| * :py:meth:`cadquery.Sketch.apply` | |
| * :py:meth:`cadquery.Sketch.invoke` | |
| Here is the same plugin rewritten using one of those methods. | |
| .. cadquery:: | |
| import cadquery as cq | |
| from cadquery.func import box | |
| def makeCubes(length): | |
| # inner method that creates the cubes | |
| def callback(wp): | |
| return wp.eachpoint(box(length, length, length), True) | |
| return callback | |
| # use the plugin | |
| result = ( | |
| cq.Workplane("XY") | |
| .box(6.0, 8.0, 0.5) | |
| .faces(">Z") | |
| .rect(4.0, 4.0, forConstruction=True) | |
| .vertices() | |
| .invoke(makeCubes(1.0)) | |
| .combine() | |
| ) | |
| Such an approach is more friendly for auto-completion and static analysis tools. | |