--- jupyter: description: 'A step block as a 3D CAD exercise: a dimensioned drawing in three views to model in planex2d or any CAD system, a solution whose features are dimensioned from the edges of the faces they sit on, and the volume, mass and center of mass to check a model against.' --- # Step block A step block holds a link at a step above its base. Two bolt holes in the low step fix it, the link sits in the slot of the high step, and a pin through the cross hole holds it. Nothing on the block is symmetric about its origin, and each feature is dimensioned from the edges of the face it sits on, as the drawing gives it. 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 step block exercise: a dimensioned drawing of a block with a low and a high step, a slot, two bolt holes and a cross hole in a front view, a top view, a side view and an isometric](../_static/gallery/step-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$. It has $x$ to the right and $z$ up, with the origin at the block's bottom left front corner. The outline runs from the origin along the bottom, up the right end, back over the high step, down the step and over the low step. With `auto_constraint={"horizontal": True, "vertical": True}`, the lines drawn level or plumb stay so, and their directions are set as they are drawn. The tops of both steps and the face between them get names, which the faces padded from them carry; the features are sketched on those faces. The block is 80 long and 40 high, and its low step is 35 long and 20 high. With these, nothing in the sketch is left free (Figure 1): ```python front = px.XZ profile = px.Sketch(front, auto_constraint={"horizontal": True, "vertical": True}) profile.Line(80, id="bottom") profile.Turn(90).Line(40, id="right_end") # Turn(90) turns the pen left, Turn(-90) right profile.Turn(90).Line(45, id="high_top", name="high_top") profile.Turn(90).Line(20, id="step", name="step") profile.Turn(-90).Line(35, id="low_top", name="low_top") profile.Close(id="left_end") profile.Constrain("bottom.start", "origin", px.Coincident) profile.Constrain("bottom", px.Distance, 80, id="length") profile.Constrain("right_end", px.Distance, 40, id="height") profile.Constrain("left_end", px.Distance, 20, id="low_height") profile.Constrain("low_top", px.Distance, 35, id="low_length") 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 50 deep. It extrudes along the plane's normal, toward the viewer, and `reverse=True` extrudes it the other way, so that the block lies behind the front plane, from $y = 0$ to 50 (Figure 2): ```python block = px.Pad(profile, 50, reverse=True) print("volume", block.mass, "area × 50", profile.Face().mass * 50) ``` ```python nbsphinx="hide-input" Views(("The profile extruded 50 deep", [(block, "shade"), (block, "ink")]), caption="The block") ``` ## The slot The slot is sketched on the high step's top. `Project` brings two of the face's edges into the sketch as fixed edges, the one at the step, lowest in $x$, and the front one, lowest in $y$. A projection's edges are numbered from 0, so the step's edge is `step_edge.Edge.0`. The slot is 10 wide with its end centers 25 apart. It is drawn roughly, and its spine runs parallel to the step's edge, 15 from it, and starts 10 from the front edge. `until=px.last` cuts it down through the block (Figure 3): ```python top = block.faces["high_top"][0] # the one face of that name slot = px.Sketch(top) slot.Project(top.edges.Min(occ.X), id="step_edge") slot.Project(top.edges.Min(occ.Y), id="front_edge") slot.Move((52, 10), dir=(0, 1)).Slot(20, 10, id="slot") slot.Constrain("slot.spine", "step_edge.Edge.0", px.Parallel) slot.Constrain("slot.spine.start", "step_edge.Edge.0", px.Distance, 15, id="slot_x") slot.Constrain("slot.spine.start", "front_edge.Edge.0", px.Distance, 10, id="slot_y") slot.Constrain("slot.spine", px.Distance, 25, id="slot_l") print("status", slot.status, "| degrees of freedom", slot.dof) block = px.Pocket(block, slot, until=px.last) # through to the last flat face it crosses ``` ```python nbsphinx="hide-input" Row((slot, "The slot on the high step, the block under it", {"plot": {"show_parent": block, "show_constraints": True}}), (block, "The slot through the high step", {"views": [(block, "shade"), (block, "ink")], "angles": (-40, 0, 20)}), caption="The slot") ``` ## The bolt holes The bolt holes, 9 across, are sketched on the low step's top in the same way, from three of its edges. Both are 12 from the left end, the front one 12 from the front edge and the back one 12 from the back edge. They keep their place if the depth of the block changes (Figure 4): ```python low = block.faces["low_top"][0] holes = px.Sketch(low) holes.Project(low.edges.Min(occ.X), id="left_edge") holes.Project(low.edges.Min(occ.Y), id="front_edge") holes.Project(low.edges.Max(occ.Y), id="back_edge") holes.Circle(9 / 2, center=(10, 10), id="front_hole") holes.Circle(9 / 2, center=(10, 40), id="back_hole") holes.Constrain("front_hole", px.Diameter, 9, id="hole_d") holes.Constrain("back_hole", "front_hole", px.Equal) holes.Constrain("front_hole.center", "left_edge.Edge.0", px.Distance, 12, id="hole_x") holes.Constrain("back_hole.center", "left_edge.Edge.0", px.Distance, 12) holes.Constrain("front_hole.center", "front_edge.Edge.0", px.Distance, 12, id="front_y") holes.Constrain("back_hole.center", "back_edge.Edge.0", px.Distance, 12, id="back_y") print("status", holes.status, "| degrees of freedom", holes.dof) block = px.Pocket(block, holes, until=px.last) ``` ```python nbsphinx="hide-input" Row((holes, "The bolt holes on the low step, the block under it", {"plot": {"show_parent": block, "show_constraints": True}}), (block, "Both bolt holes through the low step", {"views": [(block, "shade"), (block, "ink")], "angles": (-40, 0, 20)}), caption="The bolt holes") ``` ## The cross hole The cross hole for the pin, 8 across, is sketched on the step, the face between the two tops, and runs from it through the high step to the right end, across the slot. The sketch on this face has its default axes. Its origin is the global origin projected onto the face, and its $h$ axis runs along $y$, since $x$ is the face's normal. Its $v$ axis then points down: ```python step = block.faces["step"][0] cross = px.Sketch(step) print("(0, 0) lies at", cross.ToXYZ((0, 0)), "| (10, 0) at", cross.ToXYZ((10, 0)), "| (0, 10) at", cross.ToXYZ((0, 10))) ``` Dimensioned from the face's projected edges, the hole does not depend on those axes. Its center is 10 below the top edge and 25 from the front edge. It is placed roughly, below the step's top edge, which lies at $v = -40$ (Figure 5): ```python cross.Project(step.edges.Max(occ.Z), id="top_edge") cross.Project(step.edges.Min(occ.Y), id="front_edge") cross.Circle(8 / 2, center=(20, -28), id="cross_hole") cross.Constrain("cross_hole", px.Diameter, 8, id="cross_d") cross.Constrain("cross_hole.center", "top_edge.Edge.0", px.Distance, 10, id="cross_z") cross.Constrain("cross_hole.center", "front_edge.Edge.0", px.Distance, 25, id="cross_y") print("status", cross.status, "| degrees of freedom", cross.dof, "| center", cross.Measure("cross_hole")["center"]) block = px.Pocket(block, cross, until=px.last) ``` ```python nbsphinx="hide-input" Row((cross, "The cross hole on the step, in the sketch's axes, v down", {"plot": {"show_parent": block, "show_constraints": True}}), (block, "The cross hole from the step through the slot", {"views": [(block, "shade"), (block, "ink")], "angles": (-50, 0, 50)}), caption="The cross hole") ``` ## The checks These are the volume, the mass in steel S235 (7.85 g/cm³) and the center of mass, with the origin at the bottom left front corner, to compare with your own model. The volume is the profile's area times 50, less three holes. The slot runs through the 40 of the high step, the bolt holes through the 20 of the low step, and the cross hole along the 45 of the high step, but for the 10 where it crosses the slot: ```python exact = 2500 * 50 - (25 * 10 + math.pi * 5**2) * 40 - 2 * math.pi * 4.5**2 * 20 - math.pi * 4**2 * (45 - 10) c = block.center print("volume", round(block.mass, 1), "mm³, exact", round(exact, 1)) print("mass", round(block.mass * 7.85e-6, 3), "kg") print("center of mass", tuple(round(v, 2) for v in (c.x, c.y, c.z))) ``` The block as STEP, for another CAD system: ```python block.WriteStep("step_block.step") ``` The block as STEP: [step_block.step](step_block.step) ```python nbsphinx="hidden" import sys sys.path.insert(0, "..") import matplotlib.pyplot as plt from drawings import ISOMETRIC, Pictorial, SheetFigure, View from gallery import save_figure # the views drawn from the block; the dimensions from the sketches, seen along each view left = px.Plane(normal=(-1, 0, 0), axis=(0, -1, 0)) # the view from the left: -y to the right, z up (s0, s1), w = (slot.Location("slot.spine" + e) for e in (".start", ".end")), slot.dimensions["slot.w"] bolts = [holes.Measure(h)["center"] for h in ("front_hole", "back_hole")] pin = cross.Measure("cross_hole")["center"] low_z, top_z, x1 = profile.Location("low_top.start")[1], profile.Location("high_top.start")[1], profile.Location("bottom.end")[0] step_x = profile.Location("step.start")[0] front_view = View([(profile, {"length": -12, "low_length": 30, "low_height": -12, "height": 92})], solid=block, plane=front, axes=[((b[0], -3), (b[0], low_z + 3)) for b in bolts[:1]] + [((s0[0], -3), (s0[0], top_z + 3)), ((step_x - 3, pin[2]), (x1 + 3, pin[2]))]) top_view = View([(slot, {"slot_x": 62, "slot_y": 92, "slot_l": 92}, {"slot.startcap": "cross", "slot.endcap": "cross"}), (holes, {"hole_x": 62, "front_y": -12, "back_y": -12, "hole_d": (300, "leader", 2)})], solid=block, plane=px.XY, at=(0, -125), axes=[((s0[0], s0[1] - 8), (s1[0], s1[1] + 8)), ((step_x - 3, pin[1]), (x1 + 3, pin[1]))], measures=[((s1[0] - w / 2, s1[1]), (s1[0] + w / 2, s1[1]), 72, w)]) side_view = View([(cross, {"cross_z": 12, "cross_y": 52, "cross_d": (135, "leader")})], solid=block, plane=left, at=(155, 0), axes=[((-b[1], -3), (-b[1], low_z + 3)) for b in bolts]) pictorial = Pictorial(block, at=(130, -100), width=70, angles=(ISOMETRIC[0], 0, 45)) drawing = SheetFigure([front_view, top_view, side_view, pictorial], margin=25, notes=("planex2d.org", "3D exercise 04"), first_angle=True) print("scale", drawing.planex_scale, "| paper", [round(v * 25.4) for v in drawing.get_size_inches()], "mm") save_figure(drawing, "step-block", dpi=200) plt.close(drawing) ```