2020-11-30 13:41:34 +01:00
|
|
|
.. _assytutorial:
|
|
|
|
|
|
|
|
|
|
***********************
|
|
|
|
|
Assembly Tutorial
|
|
|
|
|
***********************
|
|
|
|
|
|
|
|
|
|
Introduction
|
|
|
|
|
============
|
|
|
|
|
|
|
|
|
|
The purpose of this section is to demonstrate how to use the assembly and constraints
|
|
|
|
|
functionality to build a realistic model. It will be a enclosure door assembly made out of 20x20 v-slot profiles.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Defining parameters
|
|
|
|
|
===================
|
|
|
|
|
|
|
|
|
|
We want to start with defining the model parameters to allow for easy dimension changes later:
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
import cadquery as cq
|
|
|
|
|
|
|
|
|
|
# Parameters
|
|
|
|
|
H = 400
|
|
|
|
|
W = 200
|
|
|
|
|
D = 350
|
|
|
|
|
|
|
|
|
|
PROFILE = cq.importers.importDXF("vslot-2020_1.dxf").wires()
|
|
|
|
|
|
|
|
|
|
SLOT_D = 5
|
|
|
|
|
PANEL_T = 3
|
|
|
|
|
|
|
|
|
|
HANDLE_D = 20
|
|
|
|
|
HANDLE_L = 50
|
|
|
|
|
HANDLE_W = 4
|
|
|
|
|
|
|
|
|
|
It is interesting to note that the v-slot profile is imported from a DXF file.
|
|
|
|
|
This way it is very easy to change to other aluminum extrusion type, e.g. Item or Bosch.
|
|
|
|
|
Vendors usually provide DXF files.
|
|
|
|
|
|
|
|
|
|
Defining reusable components
|
|
|
|
|
============================
|
|
|
|
|
|
|
|
|
|
Next we want to define functions generating the assembly components based on the specified parameters.
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
def make_vslot(l):
|
|
|
|
|
|
|
|
|
|
return PROFILE.toPending().extrude(l)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def make_connector():
|
|
|
|
|
|
|
|
|
|
rv = (
|
|
|
|
|
cq.Workplane()
|
|
|
|
|
.box(20, 20, 20)
|
|
|
|
|
.faces("<X")
|
|
|
|
|
.workplane()
|
|
|
|
|
.cboreHole(6, 15, 18)
|
|
|
|
|
.faces("<Z")
|
2020-12-28 20:45:28 +01:00
|
|
|
.workplane(centerOption="CenterOfMass")
|
2020-11-30 13:41:34 +01:00
|
|
|
.cboreHole(6, 15, 18)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# tag mating faces
|
|
|
|
|
rv.faces(">X").tag("X").end()
|
|
|
|
|
rv.faces(">Z").tag("Z").end()
|
|
|
|
|
|
|
|
|
|
return rv
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def make_panel(w, h, t, cutout):
|
|
|
|
|
|
|
|
|
|
rv = (
|
|
|
|
|
cq.Workplane("XZ")
|
|
|
|
|
.rect(w, h)
|
|
|
|
|
.extrude(t)
|
|
|
|
|
.faces(">Y")
|
|
|
|
|
.vertices()
|
|
|
|
|
.rect(2*cutout,2*cutout)
|
|
|
|
|
.cutThruAll()
|
|
|
|
|
.faces("<Y")
|
|
|
|
|
.workplane()
|
|
|
|
|
.pushPoints([(-w / 3, HANDLE_L / 2), (-w / 3, -HANDLE_L / 2)])
|
|
|
|
|
.hole(3)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# tag mating edges
|
|
|
|
|
rv.faces(">Y").edges("%CIRCLE").edges(">Z").tag("hole1")
|
|
|
|
|
rv.faces(">Y").edges("%CIRCLE").edges("<Z").tag("hole2")
|
|
|
|
|
|
|
|
|
|
return rv
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def make_handle(w, h, r):
|
|
|
|
|
|
|
|
|
|
pts = ((0, 0), (w, 0), (w, h), (0, h))
|
|
|
|
|
|
|
|
|
|
path = cq.Workplane().polyline(pts)
|
|
|
|
|
|
|
|
|
|
rv = (
|
|
|
|
|
cq.Workplane("YZ")
|
|
|
|
|
.rect(r, r)
|
|
|
|
|
.sweep(path, transition="round")
|
|
|
|
|
.tag("solid")
|
|
|
|
|
.faces("<X")
|
|
|
|
|
.workplane()
|
|
|
|
|
.faces("<X", tag="solid")
|
|
|
|
|
.hole(r / 1.5)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# tag mating faces
|
|
|
|
|
rv.faces("<X").faces(">Y").tag("mate1")
|
|
|
|
|
rv.faces("<X").faces("<Y").tag("mate2")
|
|
|
|
|
|
|
|
|
|
return rv
|
|
|
|
|
|
|
|
|
|
Initial assembly
|
|
|
|
|
================
|
|
|
|
|
|
|
|
|
|
Next we want to instantiate all the components and add them to the assembly.
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
# define the elements
|
|
|
|
|
door = (
|
|
|
|
|
cq.Assembly()
|
|
|
|
|
.add(make_vslot(H), name="left")
|
|
|
|
|
.add(make_vslot(H), name="right")
|
|
|
|
|
.add(make_vslot(W), name="top")
|
|
|
|
|
.add(make_vslot(W), name="bottom")
|
|
|
|
|
.add(make_connector(), name="con_tl", color=cq.Color("black"))
|
|
|
|
|
.add(make_connector(), name="con_tr", color=cq.Color("black"))
|
|
|
|
|
.add(make_connector(), name="con_bl", color=cq.Color("black"))
|
|
|
|
|
.add(make_connector(), name="con_br", color=cq.Color("black"))
|
|
|
|
|
.add(
|
|
|
|
|
make_panel(W + SLOT_D, H + SLOT_D, PANEL_T),
|
|
|
|
|
name="panel",
|
|
|
|
|
color=cq.Color(0, 0, 1, 0.2),
|
|
|
|
|
)
|
|
|
|
|
.add(
|
|
|
|
|
make_handle(HANDLE_D, HANDLE_L, HANDLE_W),
|
|
|
|
|
name="handle",
|
|
|
|
|
color=cq.Color("yellow"),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
Constraints definition
|
|
|
|
|
======================
|
|
|
|
|
|
|
|
|
|
Then we want to define all the constraints
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
# define the constraints
|
|
|
|
|
(
|
|
|
|
|
door
|
|
|
|
|
# left profile
|
|
|
|
|
.constrain("left@faces@<Z", "con_bl?Z", "Plane")
|
|
|
|
|
.constrain("left@faces@<X", "con_bl?X", "Axis")
|
|
|
|
|
.constrain("left@faces@>Z", "con_tl?Z", "Plane")
|
|
|
|
|
.constrain("left@faces@<X", "con_tl?X", "Axis")
|
|
|
|
|
# top
|
|
|
|
|
.constrain("top@faces@<Z", "con_tl?X", "Plane")
|
|
|
|
|
.constrain("top@faces@<Y", "con_tl@faces@>Y", "Axis")
|
|
|
|
|
# bottom
|
|
|
|
|
.constrain("bottom@faces@<Y", "con_bl@faces@>Y", "Axis")
|
|
|
|
|
.constrain("bottom@faces@>Z", "con_bl?X", "Plane")
|
|
|
|
|
# right connectors
|
|
|
|
|
.constrain("top@faces@>Z", "con_tr@faces@>X", "Plane")
|
|
|
|
|
.constrain("bottom@faces@<Z", "con_br@faces@>X", "Plane")
|
|
|
|
|
.constrain("left@faces@>Z", "con_tr?Z", "Axis")
|
|
|
|
|
.constrain("left@faces@<Z", "con_br?Z", "Axis")
|
|
|
|
|
# right profile
|
|
|
|
|
.constrain("right@faces@>Z", "con_tr@faces@>Z", "Plane")
|
|
|
|
|
.constrain("right@faces@<X", "left@faces@<X", "Axis")
|
|
|
|
|
# panel
|
|
|
|
|
.constrain("left@faces@>X[-4]", "panel@faces@<X", "Plane")
|
|
|
|
|
.constrain("left@faces@>Z", "panel@faces@>Z", "Axis")
|
|
|
|
|
# handle
|
|
|
|
|
.constrain("panel?hole1", "handle?mate1", "Plane")
|
|
|
|
|
.constrain("panel?hole2", "handle?mate2", "Point")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
Should you need to do something unusual that is not possible with the string
|
|
|
|
|
based selectors (e.g. use :py:class:`cadquery.selectors.BoxSelector` or a user-defined selector class),
|
|
|
|
|
it is possible to pass :py:class:`cadquery.Shape` objects to the :py:meth:`cadquery.Assembly.constrain` method directly. For example, the above
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
.constrain('part1@faces@>Z','part3@faces@<Z','Axis')
|
|
|
|
|
|
|
|
|
|
is equivalent to
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
.constrain('part1',part1.faces('>z').val(),'part3',part3.faces('<Z').val(),'Axis')
|
|
|
|
|
|
|
|
|
|
This method requires a :py:class:`cadquery.Shape` object, so remember to use the :py:meth:`cadquery.Workplane.val`
|
|
|
|
|
method to pass a single :py:class:`cadquery.Shape` and not the whole :py:class:`cadquery.Workplane` object.
|
|
|
|
|
|
|
|
|
|
Final result
|
|
|
|
|
============
|
|
|
|
|
|
|
|
|
|
Below is the complete code including the final solve step.
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
import cadquery as cq
|
|
|
|
|
|
|
|
|
|
# Parameters
|
|
|
|
|
H = 400
|
|
|
|
|
W = 200
|
|
|
|
|
D = 350
|
|
|
|
|
|
|
|
|
|
PROFILE = cq.importers.importDXF("vslot-2020_1.dxf").wires()
|
|
|
|
|
|
|
|
|
|
SLOT_D = 6
|
|
|
|
|
PANEL_T = 3
|
|
|
|
|
|
|
|
|
|
HANDLE_D = 20
|
|
|
|
|
HANDLE_L = 50
|
|
|
|
|
HANDLE_W = 4
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def make_vslot(l):
|
|
|
|
|
|
|
|
|
|
return PROFILE.toPending().extrude(l)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def make_connector():
|
|
|
|
|
|
|
|
|
|
rv = (
|
|
|
|
|
cq.Workplane()
|
|
|
|
|
.box(20, 20, 20)
|
|
|
|
|
.faces("<X")
|
|
|
|
|
.workplane()
|
|
|
|
|
.cboreHole(6, 15, 18)
|
|
|
|
|
.faces("<Z")
|
2020-12-28 20:45:28 +01:00
|
|
|
.workplane(centerOption="CenterOfMass")
|
2020-11-30 13:41:34 +01:00
|
|
|
.cboreHole(6, 15, 18)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# tag mating faces
|
|
|
|
|
rv.faces(">X").tag("X").end()
|
|
|
|
|
rv.faces(">Z").tag("Z").end()
|
|
|
|
|
|
|
|
|
|
return rv
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def make_panel(w, h, t, cutout):
|
|
|
|
|
|
|
|
|
|
rv = (
|
|
|
|
|
cq.Workplane("XZ")
|
|
|
|
|
.rect(w, h)
|
|
|
|
|
.extrude(t)
|
|
|
|
|
.faces(">Y")
|
|
|
|
|
.vertices()
|
|
|
|
|
.rect(2*cutout,2*cutout)
|
|
|
|
|
.cutThruAll()
|
|
|
|
|
.faces("<Y")
|
|
|
|
|
.workplane()
|
|
|
|
|
.pushPoints([(-w / 3, HANDLE_L / 2), (-w / 3, -HANDLE_L / 2)])
|
|
|
|
|
.hole(3)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# tag mating edges
|
|
|
|
|
rv.faces(">Y").edges("%CIRCLE").edges(">Z").tag("hole1")
|
|
|
|
|
rv.faces(">Y").edges("%CIRCLE").edges("<Z").tag("hole2")
|
|
|
|
|
|
|
|
|
|
return rv
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def make_handle(w, h, r):
|
|
|
|
|
|
|
|
|
|
pts = ((0, 0), (w, 0), (w, h), (0, h))
|
|
|
|
|
|
|
|
|
|
path = cq.Workplane().polyline(pts)
|
|
|
|
|
|
|
|
|
|
rv = (
|
|
|
|
|
cq.Workplane("YZ")
|
|
|
|
|
.rect(r, r)
|
|
|
|
|
.sweep(path, transition="round")
|
|
|
|
|
.tag("solid")
|
|
|
|
|
.faces("<X")
|
|
|
|
|
.workplane()
|
|
|
|
|
.faces("<X", tag="solid")
|
|
|
|
|
.hole(r / 1.5)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# tag mating faces
|
|
|
|
|
rv.faces("<X").faces(">Y").tag("mate1")
|
|
|
|
|
rv.faces("<X").faces("<Y").tag("mate2")
|
|
|
|
|
|
|
|
|
|
return rv
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# define the elements
|
|
|
|
|
door = (
|
|
|
|
|
cq.Assembly()
|
|
|
|
|
.add(make_vslot(H), name="left")
|
|
|
|
|
.add(make_vslot(H), name="right")
|
|
|
|
|
.add(make_vslot(W), name="top")
|
|
|
|
|
.add(make_vslot(W), name="bottom")
|
|
|
|
|
.add(make_connector(), name="con_tl", color=cq.Color("black"))
|
|
|
|
|
.add(make_connector(), name="con_tr", color=cq.Color("black"))
|
|
|
|
|
.add(make_connector(), name="con_bl", color=cq.Color("black"))
|
|
|
|
|
.add(make_connector(), name="con_br", color=cq.Color("black"))
|
|
|
|
|
.add(
|
|
|
|
|
make_panel(W + 2*SLOT_D, H + 2*SLOT_D, PANEL_T, SLOT_D),
|
|
|
|
|
name="panel",
|
|
|
|
|
color=cq.Color(0, 0, 1, 0.2),
|
|
|
|
|
)
|
|
|
|
|
.add(
|
|
|
|
|
make_handle(HANDLE_D, HANDLE_L, HANDLE_W),
|
|
|
|
|
name="handle",
|
|
|
|
|
color=cq.Color("yellow"),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# define the constraints
|
|
|
|
|
(
|
|
|
|
|
door
|
|
|
|
|
# left profile
|
|
|
|
|
.constrain("left@faces@<Z", "con_bl?Z", "Plane")
|
|
|
|
|
.constrain("left@faces@<X", "con_bl?X", "Axis")
|
|
|
|
|
.constrain("left@faces@>Z", "con_tl?Z", "Plane")
|
|
|
|
|
.constrain("left@faces@<X", "con_tl?X", "Axis")
|
|
|
|
|
# top
|
|
|
|
|
.constrain("top@faces@<Z", "con_tl?X", "Plane")
|
|
|
|
|
.constrain("top@faces@<Y", "con_tl@faces@>Y", "Axis")
|
|
|
|
|
# bottom
|
|
|
|
|
.constrain("bottom@faces@<Y", "con_bl@faces@>Y", "Axis")
|
|
|
|
|
.constrain("bottom@faces@>Z", "con_bl?X", "Plane")
|
|
|
|
|
# right connectors
|
|
|
|
|
.constrain("top@faces@>Z", "con_tr@faces@>X", "Plane")
|
|
|
|
|
.constrain("bottom@faces@<Z", "con_br@faces@>X", "Plane")
|
|
|
|
|
.constrain("left@faces@>Z", "con_tr?Z", "Axis")
|
|
|
|
|
.constrain("left@faces@<Z", "con_br?Z", "Axis")
|
|
|
|
|
# right profile
|
|
|
|
|
.constrain("right@faces@>Z", "con_tr@faces@>Z", "Plane")
|
|
|
|
|
.constrain("right@faces@<X", "left@faces@<X", "Axis")
|
|
|
|
|
# panel
|
|
|
|
|
.constrain("left@faces@>X[-4]", "panel@faces@<X", "Plane")
|
|
|
|
|
.constrain("left@faces@>Z", "panel@faces@>Z", "Axis")
|
|
|
|
|
# handle
|
|
|
|
|
.constrain("panel?hole1", "handle?mate1", "Plane")
|
|
|
|
|
.constrain("panel?hole2", "handle?mate2", "Point")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# solve
|
|
|
|
|
door.solve()
|
|
|
|
|
|
|
|
|
|
show_object(door,name='door')
|
|
|
|
|
|
|
|
|
|
This code generates the following assembly.
|
|
|
|
|
|
|
|
|
|
.. image:: _static/door_assy.png
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Data export
|
|
|
|
|
===========
|
|
|
|
|
|
|
|
|
|
The resulting assembly can be exported as a STEP file or in a internal OCCT XML format.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
STEP can be loaded in all CAD tool, e.g. in FreeCAD and the XML be used in other applications using OCCT.
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
:linenos:
|
|
|
|
|
|
|
|
|
|
door.save('door.step')
|
|
|
|
|
door.save('door.xml')
|
|
|
|
|
|
|
|
|
|
In the case of STEP colors are preserved but not transparency.
|
|
|
|
|
|
2020-12-28 20:45:28 +01:00
|
|
|
.. image:: _static/door_assy_freecad.png
|