コード例 #1
0
    def __init__(self, workplane, measures):
        """
        A parametric, grooved wall element that can be integrated into panel walls.

        .. todo: Since this is not a part that can be used as it is, it should rather be 
            implemented as a CadQuery plugin.
        .. todo:: Parameter documentation.
        .. todo:: Add parameters for edge and corner rounding.
        """

        self.model = workplane
        self.measures = measures

        # Add optional measures if missing, using their default values.
        if not hasattr(measures, 'center_offset'):
            measures.center_offset = cq.Vector(0, 0, 0)
        if not hasattr(measures, 'grooves'): measures.grooves = Measures()
        if not hasattr(measures.grooves, 'left'): measures.grooves.left = False
        if not hasattr(measures.grooves, 'right'):
            measures.grooves.right = False
        if not hasattr(measures.grooves, 'top'): measures.grooves.top = False
        if not hasattr(measures.grooves, 'bottom'):
            measures.grooves.bottom = False

        self.build()
コード例 #2
0
# =============================================================================
# Part Creation
# =============================================================================
cq.Workplane.part = utilities.part

measures = Measures(
    block = Measures(
        width = 20,
        height = 20,
        depth = 50
    ),
    hole = Measures(
        head_depth = 5,
        head_across_flats = 10.3,
        diameter = 6.1
    ),
    brackets = Measures(
        positions = Measures(top = True, bottom = True, left = False, right = False),
        height = 30,
        thickness = 6,
        hole_count = 1,
        hole_diameter = 5.1,
        fillet_radius = 10
    ),
    outer_edge_radius = 1.5
)
show_options = {"color": "lightgray", "alpha": 0}

bolt_mount = cq.Workplane("XY").part(BoltMount, measures)
show_object(bolt_mount, name = "bolt_mount", options = show_options)
コード例 #3
0
    def __init__(self, workplane, measures):
        """
        A simple, parametric L-profile bracket to connect two flat plates.

        The bracket will have a horizontal leg pointing from the joint to the front (into -y) and a 
        vertical leg pointing from the joint to the top (into +z) of the provided workplane.

        :param workplane: The CadQuery workplane to create this part on.
        :param measures: The measures to use for the parameters of this design. Expects a nested 
            [SimpleNamespace](https://docs.python.org/3/library/types.html#types.SimpleNamespace) 
            object, which may have the following attributes:
            - **``center_fillet``:** Fillet radius for the main structural fillet of the bracket.
            - **``corner_radius``:** Radius for rounding the bracket's outside corners.
            - **``edge_radius``:** Radius for rounding all outer edges except those touching the plates.
            - **``horizontal_leg.width``:** Width of the horizontal leg of the bracket. Different widths 
                of the two legs are not yet supported.
            - **``horizontal_leg.depth``:** Depth of the horizontal leg of the bracket.
            - **``horizontal_leg.height``:** Height (material thickness) of the horizontal leg of the bracket.
            - **``horizontal_leg.hole_count``:** Number of holes in the horizontal leg of the bracket. 
                All holes will be arranged in a line.
            - **``horizontal_leg.hole_diameters``:** Diameter of the holes in the horizontal leg of the 
                bracket. Provided as a single float when using a common value for all holes; otherwise 
                as a list with one element for each hole in the bracket, starting with the one closest 
                to its front.
            - **``horizontal_leg.nuthole_sizes``:** Size between flats of the nutholes that are part 
                of the holes in the horizontal leg of the bracket. See ``hole_diamaters`` for the format.
            - **``horizontal_leg.clamp_lengths``:** Length between the outer surface of the part 
                (touching a connected plate) and the start of the nut hole. See ``hole_diamaters`` for the format.
            - **``vertical_leg.*``:** Specs for the vertical leg of the bracket, using the same 
                elements and semantics as for the horizontal leg.

        .. todo:: Support specifying custom hole spacings, for each hole relative to the previous one.
            If given, this will override the automatic positioning. Then use it to position the 
            holes closest to the joint closer to it. Currently, the material thickness of the other 
            leg prevents that from happening.
        .. todo:: Support chamfering in addition to filleting for the main structural support fillet.
        .. todo:: Support different widths of the two legs of the bracket.
        """

        cq.Workplane.bracket = utilities.bracket
        cq.Workplane.transformedWorkplane = utilities.transformedWorkplane
        cq.Workplane.bolt = utilities.bolt
        cq.Workplane.cutEachAdaptive = utilities.cutEachAdaptive

        self.model = workplane
        self.debug = False
        self.measures = measures
        m = self.measures

        # The bracket lengths are measured at the outside, but the construction actually uses a
        # central cuboid block with two attached brackets. Adapting the measures accordingly.
        m.center_block = Measures(
            # Naming is as seen from the horizontal leg.
            width=max(m.horizontal_leg.width, m.vertical_leg.width),
            depth=m.vertical_leg.height,
            height=m.horizontal_leg.height)
        m.horizontal_leg.depth -= m.center_block.depth
        m.vertical_leg.depth -= m.center_block.height

        # Create hole specs which combine the other hole measures in the format expected by bolthole().
        m.horizontal_leg.hole_specs = [
            {
                "diameter":
                m.horizontal_leg.hole_diameters[i] if isinstance(
                    m.horizontal_leg.hole_diameters,
                    list) else m.horizontal_leg.hole_diameters,
                "clamp_length":
                m.horizontal_leg.clamp_lengths[i] if isinstance(
                    m.horizontal_leg.clamp_lengths,
                    list) else m.horizontal_leg.clamp_lengths,
                "nuthole_size":
                m.horizontal_leg.nuthole_sizes[i] if isinstance(
                    m.horizontal_leg.nuthole_sizes,
                    list) else m.horizontal_leg.nuthole_sizes,
                "nuthole_depth":
                1.1 * m.vertical_leg.
                depth  # Just choose something large enough for cutting. 
            } for i in range(m.horizontal_leg.hole_count)
        ]
        m.vertical_leg.hole_specs = [
            {
                "diameter":
                m.vertical_leg.hole_diameters[i] if isinstance(
                    m.vertical_leg.hole_diameters,
                    list) else m.vertical_leg.hole_diameters,
                "clamp_length":
                m.vertical_leg.clamp_lengths[i] if isinstance(
                    m.vertical_leg.clamp_lengths,
                    list) else m.vertical_leg.clamp_lengths,
                "nuthole_size":
                m.vertical_leg.nuthole_sizes[i] if isinstance(
                    m.vertical_leg.nuthole_sizes,
                    list) else m.vertical_leg.nuthole_sizes,
                "nuthole_depth":
                1.1 * m.horizontal_leg.
                depth  # Just choose something large enough for cutting. 
            } for i in range(m.vertical_leg.hole_count)
        ]

        # TODO: Initialize missing measures with defaults.

        self.build()
コード例 #4
0
# =============================================================================
cq.Workplane.part = utilities.part

bolt_length = 10.0

measures = Measures(
    center_fillet=51.9,  # Max. 52.0, which is the inner depth of the bracket.
    corner_radius=5.0,
    edge_radius=1.5,
    horizontal_leg=Measures(
        width=15.0,
        depth=60.0,
        height=8.0,
        hole_count=2,
        hole_diameters=3.2,  # Good for M3 and some printer artefacts.
        nuthole_sizes=
        5.8,  # 5.4 mm for a M3 nut, 0.4 mm for easy inserting. Corrected from 0.2.
        # Clamp length 7.5 is for M3×16, 11.5 is for M3×20, both with a countersunk head and
        # for additional 5 mm plate material thickness. The bolt will protrude ca. 1 mm over the nut.
        clamp_lengths=[11.5, 7.5]),
    vertical_leg=Measures(width=15.0,
                          depth=60.0,
                          height=8.0,
                          hole_count=2,
                          hole_diameters=3.2,
                          nuthole_sizes=5.8,
                          clamp_lengths=[11.5, 7.5]))

plate_bracket = cq.Workplane("XY").part(PlateBracket, measures)
show_options = {"color": "lightgray", "alpha": 0}
show_object(plate_bracket, name="plate_bracket", options=show_options)
コード例 #5
0
            # by the corresponding edge chamfers of NEMA stepper motors.
            .faces("<Z[-2]").edges("<X or >X").chamfer(m.motor_chamfer))


# =============================================================================
# Part Creation
# =============================================================================
cq.Workplane.part = utilities.part

measures = Measures(
    motor_width=60,  # For NEMA23
    motor_height=60,  # For NEMA23
    motor_depth=80,
    motor_chamfer=5,
    wall_thickness=4,
    faceplate=Measures(
        # rectangular distance between stepper mounting holes (NEMA 23 = 47.1)
        mounthole_distance=47.1,
        mounthole_diameter=5.0,
        mainhole_diameter=28.2,
        mainhole_cbore_diameter=40.0,
        mainhole_cbore_depth=2.0),
    brackets=Measures(width=25,
                      hole_count=2,
                      hole_diameter=4.5,
                      fillet_radius=7))
show_options = {"color": "lightgray", "alpha": 0}

motor_mount = cq.Workplane("XY").part(MotorMount, measures)
show_object(motor_mount, name="motor_mount", options=show_options)
コード例 #6
0
measures = Measures(
    diameter = 25.0,
    base_height = 16.0,
    clamp_gap = 1.2, # Enough, as measured decrease by clamping is 0.5 mm for a 20 mm diameter part.
    fillets = 1.25, # Default for filleted edges, if nothing more specific is found in other parameters.
    shaft = Measures(
        # Diameter for holes without a clamp. The shaft should be insertable easily, not creating 
        # any splitting force for the part. 5.3 is good for a shaft diameter of 5.0 mm.
        hole_diameter = 5.3,
        # Diameter for holes with the clamp mechanism. Usually use the exact shaft diameter here.
        # This also applies for the clip-style clamping mechanism. There will be some printer 
        # artefacts that push the clamping clip slightly outwards, but this is undone when fastening 
        # it. It ideally leads to a clamp having the configured part diameter along its whole length, 
        # because that results in the least internal stress (and thus, no part splitting).
        clamping_diameter = 5.00,
        flatten = 0.0
    ),
    clamp = Measures(
        style = "clip", # "clip" or (later) "two parts"
        groove_depth = 14.0
    ),
    bolt_holes = Measures(
        clamp_length = 14.5, # The cylindrical section of the bolt between head and nut.
        hole_size = 3.2, # Good for M3 and some printer artefacts.
        nuthole_size = 5.8, # 5.4 mm for a M3 nut, 0.4 mm for easy inserting. Corrected from 0.2.
        headhole_size = 6.3, # 6.1 for a DIN 7991 M3 countersunk bolt head, 0.2 for easy assembly.
        head_angle = 90, # DIN 7991 countersunk bolt. Cone tip angle. Omit to use a bolt with cylindrical head.
        # Manual offset from default position radially centered in the available space.
        # Moving the bolts towards the center like here can help to compensate that in its default 
        # position there is more material around a bolt towards the center. We better distribute 
        # that material to prevent part splitting equally well on all sides.
        radial_offset = -2.25,
        # Manual offset from default position near the end of the clamping groove.
        vertical_offset = 2.0,
        # Manual offset from default position in the depth direction. Bolt direction is positive.
        #  The default is to position the bolt so that half the bolt's cylindrical part between 
        # head and nut (see clamp_length) is half above and half below the workplane. You can adjust 
        # this to hide both nut and bolt head just inside the coupling outline.
        #  The right value for M3 bolts is to keep 0.35 mm of cylindrical hole depth at the inlet of 
        # the bolt hole (measured where the wall height is lowest). Not more, as this guarantees 
        # that the bolt head is only just inside the part outline. This applies when a M3 hole is 
        # configured for countersunk bolts using headhole_size = 6.3, head_angle = 90.
        depth_offset = -0.1
    ),
    coupler = Measures(
        # Coupling with a hexagonal drive profile for M5 hex bolts.
        # Using measures from ISO 4017 / DIN 933 and ISO 4014 / DIN 931. For couplings that should be 
        # short, interlocking with a hex bolt like this is preferable to a spider coupling. Also, 
        # it is one part less to 3D print, as this coupling type needs simply a bolt as counterpart.
        style = "hexagonal",
        height = 3.5, # Excludes filleted height added automatically on top.
        size = 8.4, # 8.0 mm wrench size, 0.4 mm for coupler play and easy inserting.

        # Example for a spider coupling. Suitably sized for NEMA17 stepper motors.
        # style = "spider", # "spider" / "hexagonal"
        # height = 6.0,
        # cogs = 4, # Can only be 4 or 6 so far due to a bug.
        # gap_angle = 3
    ),
)
コード例 #7
0
# Part Creation
# =============================================================================

measures = Measures(
    shell_thickness=3,  # todo: Use correct measures for EN 1451 tubes.
    # Do not visually optimize the tube end of an inclined tube to coincide with the wall or be
    # behind the wall's surface. That lets CadQuery hang depending on added grooves.
    # Let it stick out at least 0.1 mm more.
    # todo: Fix the hang condition when the tube end is not fully in front of the wall surface.
    length_before_wall=17.7,  # 21.3 at 45°, 17.7 at 40°
    length_after_wall=60,
    # todo: Fix that the geometry construction fails for angles >28° and <31°.
    angle=40,
    # todo: Count position_pos from the wall center, to make it independent of length_before_wall.
    transition_pos=48,
    transition_length=5,
    input=Measures(
        inner_diameter=32,  # todo: Use correct measures for EN 1451 tubes.
        cut_angle=-30),
    output=Measures(
        inner_diameter=26,  # todo: Use correct measures for EN 1451 tubes.
        # There can be some issues with incorrect cuts and hangs at certain angles, esp. those
        # that make the cut go through edges. Just try slightly different angles then.
        cut_angle=49),
    wall=Measures(
        thickness=11,
        groove_width=3.0 * 1.1,  # Wall panel thickness and tolerance.
        groove_depth=8,
        grooves=Measures(left=True, right=True, bottom=True)))
show_options = {"color": "lightgray", "alpha": 0}

tube_socket = cq.Workplane("XY").part(TubeSocket, measures)
コード例 #8
0
    def __init__(self, workplane, measures):
        """
        A parametric tube socket embedded into a wall, and flush with its outside.
        
        The parameters allow to make this a socket for EN 1451 wastewater tubes.

        :param workplane: The CadQuery workplane to create the chute on.
        :param measures: The measures to use for the parameters of this design. Expects a nested 
            [SimpleNamespace](https://docs.python.org/3/library/types.html#types.SimpleNamespace) 
            object, which may have the following attributes:
            - **``shell_thickness``:** Shell thickness of the tube element.
            - **``length_before_wall``:** How long the tube sticks out from the wall at the 
                input side. Measured along the tube center line to the surface of the wall before 
                tilting the tube (i.e. while it is vertical to the wall). Defaults to ``0``.
            - **``length_after_wall``:** How long the tube sticks out from the wall at the 
                output side. Measured along the tube center line to the surface of the wall before 
                tilting the tube (i.e. while it is vertical to the wall). Defaults to ``0``.
            - **``angle``:** The angle of the tube, measured in a vertical plane, 
                relative to going straight through the wall. 0° is straight, positive angles lift 
                the entry opening. This corresponds to the default rotation direction ("rotating 
                the y axis towards the z axis"). Defaults to ``0``.
            - **``transition_pos``:** Center of the section where the tube diameter changes between 
                the input and output diameters.
            - **``transition_length``:** Length of the section where the tube diameter changes 
                between the input and output diameters.
            - **``input.inner_diameter``:** Inner diameter of the tube at the input side.
            - **``input.cut_angle``:** The angle of the plane cutting the tube on the 
                input side, measured in a vertical plane, relative to a simple straight cut of 
                the tube end. Positive rotation direction is like rotating y axis towards z. 
                Defaults to ``0``.
            - **``output.inner_diameter``:** Inner diameter of the tube at the output side.
            - **``output.cut_angle``:** Like ``input_cut_angle_vertical`` but for the 
                exit side.
            - **``seal_cavity``:** Measures group to create a cavity for a sealing ring in the tube. 
                Omit to not create a seal cavity.
            - **``seal_cavity.position``:** 0 means "one ``shell_thickness`` away from the tube input".
            - **``seal_cavity.depth``:** Defines the depth of the seal cavity.
            - **``seal_cavity.inner_diameter``:** Defines the inner diameter of the seal cavity.
            - **``wall.thickness``:** Thickness of the tube socket's integrated wall element.
            - **``wall.groove_width``:** Thickness of the panel elements to be inserted into the 
                tube socket's integrated wall element.
            - **``wall.groove_depth``:** How to deep to cut the grooves around the tube socket's 
                integrated wall element.
            - **``wall.grooves``:** Where to create grooves around the tube socket's integrated 
                wall element. A Dict with elements "left", "right", "top", "bottom", each with a 
                Boolean value. Values not supplied default to ``False``.

        .. todo:: Add fillets to the edges where the tube goes through the wall.
        .. todo:: Add parameters to round the edges of the input and output tube ends.
        .. todo:: Add parameters for minimum wall height and width (including grooves). That also 
            will require parameters to determine the position of the wall insert.
        .. todo:: Add parameters that allow a horizontal tube angle in addition to the vertical one.
        .. todo:: Add parameters that allow horizontal tube end cutting angles in addition to the 
            vertical ones. However, probably that's never needed in practice.
        """

        self.model = workplane
        self.debug = False
        self.measures = measures

        # Adaptation to count length before / after wall from the wall surface, not from the wall center.
        self.measures.length_before_wall += self.measures.wall.thickness / 2
        self.measures.length_after_wall += self.measures.wall.thickness / 2

        self.measures.length = \
            self.measures.length_before_wall + self.measures.length_after_wall

        self.measures.input.outer_diameter = self.measures.input.inner_diameter + 2 * self.measures.shell_thickness
        self.measures.output.outer_diameter = self.measures.output.inner_diameter + 2 * self.measures.shell_thickness

        # Add optional measures if missing, using their default values.
        # todo: Create a utility function for this using getattr() internally, called like this:
        #   self.measures = fill_defaults(self.measures, Measures(…)).
        if not hasattr(measures, 'length_before_wall'):
            measures.length_before_wall = 0
        if not hasattr(measures, 'length_after_wall'):
            measures.length_after_wall = 0
        if not hasattr(measures, 'horizontal_angle'):
            measures.horizontal_angle = 0
        if not hasattr(measures, 'angle'): measures.angle = 0
        if not hasattr(measures, 'transition_pos'):
            measures.transition_pos = measures.length / 2
        if not hasattr(measures, 'transition_length'):
            measures.transition_pos = measures.shell_thickness
        if not hasattr(measures.input, 'cut_angle'):
            measures.input.cut_angle = 0
        if not hasattr(measures.output, 'cut_angle'):
            measures.output.cut_angle = 0
        if not hasattr(measures, 'seal_cavity'): measures.seal_cavity = None
        if not hasattr(measures.wall, 'grooves'):
            measures.wall.grooves = Measures()
        if not hasattr(measures.wall.grooves, 'left'):
            measures.wall.grooves.left = False
        if not hasattr(measures.wall.grooves, 'right'):
            measures.wall.grooves.right = False
        if not hasattr(measures.wall.grooves, 'top'):
            measures.wall.grooves.top = False
        if not hasattr(measures.wall.grooves, 'bottom'):
            measures.wall.grooves.bottom = False

        self.build()
コード例 #9
0
# and hide objects in cq-editor and be able to export them to one STEP file each.
union_results = False

measures = Measures(
    baseplate=Measures(diameter=90.0, thickness=3.0, inclination=0),
    shovels=Measures(count=6, height=38.0, size=20.0, cavity=4.0),
    shaft=Measures(
        diameter=5.0,
        flatten=
        0.01,  # TODO: Fix that this cannot be 0 due to a bug in utilities.shaft_shape.
        clamp_gap=1.0,
        collar_inner_diameter=8.0,
        collar_outer_diameter=50.5,
        collar_inner_height=15.0,
        # TODO: Fix that this cannot be the same as collar_inner_height due to a CAD kernel bug
        # when doing "zero-volume lofting". For some values, this works, and for some it leads
        # to an infinite loop and exhausting the memory. In this case, a plain cylinder should be
        # created instead.
        collar_outer_height=14.99),
    bolts=Measures(
        hole_size=3.2,
        hole_position_radial=9.0,
        hole_position_vertical=7.5,
        headhole_size=5.65,
        nuthole_width=5.65,  # M3 nut size between flats is 5.5 mm.
        nuthole_depth=2.45,  # M3 nut height is 2.3 mm.
        clamp_length=25.0  # Good for using M3×15 bolts.
    ))
show_options = {"color": "lightgray", "alpha": 0}

if union_results:
コード例 #10
0
                                m.corner_cuts.front_left).edges(
                                    parallel_z_max_x_min_y).corner_cut(
                                        m.corner_cuts.front_right))


# =============================================================================
# Part Creation
# =============================================================================
cq.Workplane.part = utilities.part

# Currently, base plate and top plate are identical. That will be different later, for example
# when sorter modules become narrower.

base_plate_measures = Measures(
    width=268,  # To slide easily into a 270 mm wide Eurobox case.
    height=180,
    thickness=5,
    corner_cuts=Measures(front_right=("fillet", 4.0),
                         front_left=("fillet", 4.0)))

top_plate_measures = Measures(
    width=268,  # To slide easily into a 270 mm wide Eurobox case.
    height=180,
    thickness=5,
    corner_cuts=Measures(front_right=("fillet", 4.0),
                         front_left=("fillet", 4.0)))

base_plate = cq.Workplane("XZ").part(Plate, base_plate_measures)
top_plate = (cq.Workplane("XY").part(Plate, top_plate_measures).translate(
    (0, -0.5 * top_plate_measures.height + 0.5 * base_plate_measures.thickness,
     0.5 * base_plate_measures.height + 0.5 * top_plate_measures.thickness)))
コード例 #11
0
measures = Measures(
    # Type does not matter, as we create a symmetric part that can be used on both sides.
    type = "right",
    width = 2 * inner_wall_to_holes, # To horizontally center the holes. Just because.
    depth = max_part_depth,
    height = height,
    fillets = Measures(
        upper = 5.5,
        vertical = 3.0,
        lower = 5.0
    ),
    ramp_1 = Measures(
        width = 5.0, # TODO: Fix that this cannot be the same as width due to a CAD kernel issue.
        height = 13.5
    ),
    ramp_2 = Measures(
        width = 5.0, # TODO: Fix that this cannot be the same as width due to a CAD kernel issue.
        height = 13.5
    ),
    hole_1 = Measures(
        horizontal_pos = inner_wall_to_holes, # From end of part near the plate surface.
        vertical_pos = height - (top_edge_to_hole_1 - top_edge_to_part), # From bottom end of part.
        diameter = 3.3,
        nuthole_size = 5.6, # 5.4 mm for a M3 nut, 0.2 mm for easy inserting.
        nuthole_depth = 3.0 # 2.3 mm for a M3 nut, and 0.7 mm for the bolt to protrude.
    ),
    hole_2 = Measures( # Same as for hole_1.
        horizontal_pos = inner_wall_to_holes,
        vertical_pos = top_edge_to_hole_1 - top_edge_to_part, # From bottom end of part.
        diameter = 3.3,
        nuthole_size = 5.6, # 5.4 mm for a M3 nut, 0.2 mm for easy inserting.
        nuthole_depth = 3.0 # 2.3 mm for a M3 nut, and 0.7 mm for the bolt to protrude.
    )
)
コード例 #12
0
# by the same amount. (Oberve the 45-90-45 triangle between head center, hole radius and cone tip.)
countersink_excess_depth = head_hole_additional_radius

# A counterunk DIN 7991 M3 bolt has a 0.6 mm thick cylindrical section at the very top of its head.
# To sink this in, you either need a counterbore hole and / or a deeper countersink hole.
head_cylindrical_height = 0.6
head_to_surface = 1.0  # Intended depth of sinking the head below the surface of the hole.
counterbore_depth = head_to_surface + head_cylindrical_height - countersink_excess_depth

measures = Measures(
    # Type does not matter, as we create a symmetric part that can be used on both sides.
    type="right",  # Means right side of plate, not right side of case.
    width=5.0,
    depth=25.0,
    height=height,
    corner_radius=Measures(
        # No upper corner radius here, as that corner is flat against a wall and won't hurt anyone.
        # And without that corner radius, printing it with its front face down becomes possible.
        upper=0.0,
        # The case's inner ege radius is 4.0 mm. Tested: 3.0, 4.0, 4.5, 5.0. For proper testing
        # always print the filleted edge facing to the top, as otherwise the shape is bent, even with support.
        case=4.0,
        lower=0.0),
    ramp_1=Measures(
        width=
        4.99,  # TODO: Fix that this cannot be the same as width due to a CAD kernel issue.
        height=13.5),
    ramp_2=Measures(
        width=
        4.99,  # TODO: Fix that this cannot be the same as width due to a CAD kernel issue.
        height=13.5),
コード例 #13
0
# Part Creation
# =============================================================================
cq.Workplane.part = utilities.part

measures = Measures(
    motor_width=42.8,  # NEMA17: 42.3; NEMA23: 60
    motor_height=42.3,  # NEMA17: 42.3; NEMA23: 60
    motor_depth=40.5,
    motor_chamfer=3,  # NEMA17: not standardized, but 4 mm is typical
    wall_thickness=2.4,  # 2.4 mm = 6 shells; next option 3.2 mm = 8 shells
    # Chamfer around the bottommost edges. Must be smaller than 0.5 * wall_thickness.
    # When using this, print with a brim to prevent adhesion issues for the brackets.
    lower_chamfer=0.8,
    # Chamfer around the topmost edges. Must be smaller than 0.5 * wall_thickness.
    upper_chamfer=0.8,
    faceplate=Measures(
        # rectangular distance between stepper mounting holes (NEMA 23 = 47.1)
        mounthole_distance=31.0,  # NEMA17: 31.0; NEMA23: 47.1
        mounthole_diameter=3.5,  # NEMA17: 3.5 for M3; NEMA23: 5.0 for M4
        mainhole_diameter=22.1,  # NEMA17: ≥5.0; NEMA23: TODO
        mainhole_cbore_diameter=22.1,  # NEMA17: 22.0; NEMA23: 40.0
        mainhole_cbore_depth=2.0),
    brackets=Measures(
        width=20,
        hole_count=2,
        hole_diameter=3.3,  # hole for M3
        fillet_radius=7))
show_options = {"color": "lightgray", "alpha": 0}

motor_h_mount = cq.Workplane("XY").part(MotorHMount, measures)
show_object(motor_h_mount, name="motor_h_mount", options=show_options)