--- jupyter: description: 'A guide block as a 3D CAD exercise: a dimensioned drawing to model in planex2d or any CAD system, a solution from a T-slot profile padded through and counterbored holes, and the volume, mass and center of mass to check a model against.' --- # Guide block A guide block carries a T-slot for the bolts or sliding nuts of a fixture and is screwed down through two counterbored holes. As a solid, it is one profile with the T-slot, padded through, and two stepped holes pocketed from the top. Model the block from the drawing in planex2d or in any CAD system, then check its volume, mass and center of mass against the values at the end of the page. A solution in planex2d follows. ![The guide block exercise: a dimensioned drawing of a block with a T-slot and two counterbored holes in a front view, a top view and an isometric](../_static/gallery/guide-block.png){.result} ```python import math import netgen.occ as occ import planex as px ``` ```python nbsphinx="hidden" from docs_figures import Row, Views ``` ## The profile The profile is the front view, sketched in the front plane, the plane with its normal toward the viewer, $-y$, with $x$ to the right and $z$ up and the origin in the middle of the bottom. The outline runs from the bottom left corner around the block and down into the T-slot, over its neck, the ledge out to the wide part, its flank and its bottom, and the same back up on the other side. With `auto_constraint=True`, the flags `px.Horizontal` and `px.Vertical` in the drawing calls become constraints. The profile is symmetric about the vertical axis. The left neck, flank, ledge and top mirror the lines of the right ones, and the mirror sets their directions too, so they carry no flags. With the sizes of the front view, nothing in the sketch is left free (Figure 1): ```python front = px.XZ profile = px.Sketch(front, auto_constraint=True) profile.Move((-50, 0)).Line(100, px.Horizontal, id="bottom") profile.Turn(90).Line(40, px.Vertical, id="right_side") # Turn(90) turns the pen left, Turn(-90) right profile.Turn(90).Line(40, px.Horizontal, id="right_top") profile.Turn(90).Line(12, px.Vertical, id="right_neck") profile.Turn(90).Line(5, px.Horizontal, id="right_ledge") profile.Turn(-90).Line(8, px.Vertical, id="right_flank") profile.Turn(-90).Line(24, px.Horizontal, id="slot_bottom") profile.Turn(-90).Line(8, id="left_flank") profile.Turn(-90).Line(5, id="left_ledge") profile.Turn(90).Line(12, id="left_neck") profile.Turn(90).Line(40, id="left_top") profile.Close(px.Vertical, id="left_side") profile.Constrain("bottom.mid", "origin", px.Coincident) profile.Constrain("bottom", px.Distance, 100, id="length") profile.Constrain("right_side", px.Distance, 40, id="height") profile.Constrain("right_neck", "left_neck", px.Distance, 14, id="opening") profile.Constrain("right_neck", px.Distance, 12, id="neck") profile.Constrain("right_flank", "left_flank", px.Distance, 24, id="slot_w") profile.Constrain("right_flank", px.Distance, 8, id="slot_h") profile.Constrain("left_neck", "right_neck", "V", px.SymmetricCarriers) profile.Constrain("left_flank", "right_flank", "V", px.SymmetricCarriers) profile.Constrain("left_ledge", "right_ledge", "V", px.SymmetricCarriers) profile.Constrain("left_top", "right_top", "V", px.SymmetricCarriers) print("status", profile.status, "| degrees of freedom", profile.dof) ``` ```python nbsphinx="hide-input" Row((profile, "Every dimension and tie of the profile", {"constraints": True}), caption="The fully constrained profile") ``` ## The block `px.Pad` extrudes the profile 30 to both sides of the front plane, 60 in all, so that the T-slot runs through the block from front to back. Its volume is the area of the profile times 60 (Figure 2): ```python block = px.Pad(profile, 30, length2=30) print("volume", block.mass, "area × 60", profile.Face().mass * 60) ``` ```python nbsphinx="hide-input" Views(("The profile extruded 60 deep", [(block, "shade"), (block, "ink")]), caption="The block") ``` ## The holes The counterbores, 15 across and 9 deep, are sketched on the top of the block, its face highest in $z$. The T-slot splits the top in two, but both halves lie in the plane of the sketch. They are placed roughly, 60 apart, and the spacing of 70 moves them to their place (Figure 3a). The block shows them pocketed (Figure 3b): ```python top = block.faces.Max(occ.Z) # one half of the top; the sketch takes its whole plane counterbores = px.Sketch(top) counterbores.Circle(15 / 2, center=(-30, 0), id="left_counterbore") counterbores.Circle(15 / 2, center=(30, 0), id="right_counterbore") counterbores.Constrain("left_counterbore", px.Diameter, 15, id="counterbore_d") counterbores.Constrain("left_counterbore", "right_counterbore", px.Equal) counterbores.Constrain("left_counterbore.center", "right_counterbore.center", px.Distance, 70, id="spacing") counterbores.Constrain("left_counterbore.center", "H", px.Coincident) counterbores.Constrain("left_counterbore.center", "right_counterbore.center", "V", px.Symmetric) block = px.Pocket(block, counterbores, 9) # 9 deep, against the face's normal: down ``` ```python nbsphinx="hide-input" Row((counterbores, "The sketch on the top, the block under it", {"plot": {"show_parent": block, "show_constraints": True}}), (block, "Both counterbores in the top", {"views": [(block, "shade"), (block, "ink")], "angles": (-40, 0, 20)}), caption="The counterbores") ``` The through holes, 9 across, go through the counterbores' centers, sketched on the same top, and `until=px.last` cuts them down through the bottom (Figure 4): ```python holes = px.Sketch(top) for side in ("left", "right"): holes.Circle(9 / 2, center=counterbores.Location(side + "_counterbore.center"), id=side + "_hole") # the solved center holes.Constrain("left_hole", px.Diameter, 9, id="hole_d") holes.Constrain("left_hole", "right_hole", px.Equal) block = px.Pocket(block, holes, until=px.last) # through to the last flat face it crosses ``` ```python nbsphinx="hide-input" Row((holes, "The sketch of the holes on the top, the block under it", {"plot": {"show_parent": block, "show_constraints": True}}), (block, "The through holes from the counterbores down", {"views": [(block, "shade"), (block, "ink")], "angles": (-40, 0, 20)}), caption="The through holes") ``` ## The checks These are the volume, the mass in steel S235 (7.85 g/cm³) and the center of mass, with the origin in the middle of the bottom, to compare with your own model. The block is symmetric about two planes, so its center of mass lies on the vertical axis: ```python c = block.center print("volume", round(block.mass, 1), "mm³") print("mass", round(block.mass * 7.85e-6, 3), "kg") print("center of mass", round(c.z, 2), "above the bottom, off the axis by", round(math.hypot(c.x, c.y), 6)) ``` The block as STEP, for another CAD system: ```python block.WriteStep("guide_block.step") ``` The block as STEP: [guide_block.step](guide_block.step) ```python nbsphinx="hidden" import sys sys.path.insert(0, "..") import matplotlib.pyplot as plt from drawings import Pictorial, SheetFigure, View from gallery import save_figure # the views from the sketches and the block: the outline of the top view, the slot seen from above, the holes x0, x1 = profile.Location("bottom.start")[0], profile.Location("bottom.end")[0] y0, y1 = block.faces.Min(occ.Y).center.y, block.faces.Max(occ.Y).center.y height = profile.Location("right_side.end")[1] neck = profile.Location("right_neck.start")[0] flank = profile.Location("right_flank.start")[0] deep = height - 9 rows = [(counterbores.Location(s + "_counterbore.center")[0], counterbores.Measure(s + "_counterbore")["radius"], holes.Measure(s + "_hole")["radius"]) for s in ("left", "right")] front_view = View( [(profile, {"length": -18, "slot_w": -32, "height": x1 + 30, "neck": x1 + 15, "slot_h": x1 + 15, "opening": height + 15}, {}, front)], hidden=[[(x - rc, height), (x - rc, deep), (x + rc, deep), (x + rc, height)] for x, rc, _ in rows] + [[(x + s * rh, deep), (x + s * rh, 0)] for x, _, rh in rows for s in (-1, 1)], axes=[((0, -5), (0, height + 5))] + [((x, -5), (x, height + 5)) for x, _, _ in rows], measures=[((rows[0][0] - rows[0][1], deep), (rows[0][0] - rows[0][1], height), x0 - 15, 9)]) top_view = View( [(counterbores, {"spacing": y0 - 15, "counterbore_d": (120, "leader", 2)}, {}), (holes, {"hole_d": (240, "leader", 2)}, {})], at=(0, -95), lines=[[(x0, y0), (x1, y0), (x1, y1), (x0, y1), (x0, y0)]] + [[(s * neck, y0), (s * neck, y1)] for s in (-1, 1)], hidden=[[(s * flank, y0), (s * flank, y1)] for s in (-1, 1)], axes=[((x0 - 5, 0), (x1 + 5, 0), (0, 0)), ((0, y0 - 5), (0, y1 + 5), (0, 0))] + [((x, y0 - 5), (x, y1 + 5), (x, 0)) for x, _, _ in rows], measures=[((x1, y0), (x1, y1), x1 + 15, y1 - y0)]) drawing = SheetFigure([front_view, top_view, Pictorial(block, at=(x1 + 100, 10), width=95)], margin=15, notes=("planex2d.org", "3D exercise 02"), first_angle=True) print("scale 1 :", round(1 / drawing.planex_scale), "| paper", [round(v * 25.4) for v in drawing.get_size_inches()], "mm") save_figure(drawing, "guide-block", dpi=200) plt.close(drawing) ```