--- jupyter: description: 'An angle bracket as a 3D CAD exercise: a dimensioned drawing in three views to model in planex2d or any CAD system, a solution from an L profile and a rib with holes sketched on the faces of the solid, and the volume, mass and center of mass to check a model against.' --- # Angle bracket An angle bracket holds a part at a right angle to a base. It has two legs, a rib that stiffens the corner between them, and holes in both legs. As a solid, it is an L profile padded, a rib padded into its corner, and holes pocketed from two faces. Model the bracket 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 angle bracket exercise: a dimensioned drawing of an L-shaped bracket with a rib and holes in both legs in a front view, a top view, a side view and an isometric](../_static/gallery/angle-bracket.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 side view of the L, sketched on `px.YZ`: $y$ runs back from the front of the bracket and $z$ up, with the origin at the bottom front corner. The outline runs along the bottom, up the back end of the base, forward along its top, up the back of the leg, over its top and down its front. With `auto_constraint={"horizontal": True, "vertical": True}`, the lines drawn level or plumb stay so. A fillet rounds the inner corner and makes its radius a dimension, `corner.r`. The base's top and the leg's back get the name `inner`, which the faces padded from them carry; the rib is placed against those faces. The base and the leg are 8 thick, the base 80 long and the leg 60 high. With these, nothing in the sketch is left free (Figure 1): ```python side = px.Sketch(px.YZ, auto_constraint={"horizontal": True, "vertical": True}) side.Line(80, id="bottom") side.Turn(90).Line(8, id="back_end") # Turn(90) turns the pen left, Turn(-90) right side.Turn(90).Line(70, id="base_top", name="inner") side.Turn(-90).Line(50, id="leg_back", name="inner") side.Turn(90).Line(8, id="leg_top") side.Close(px.Vertical, id="leg_front") # drawn only roughly plumb: the flag holds it side.Fillet("base_top.end", 6, id="corner") side.Constrain("bottom.start", "origin", px.Coincident) side.Constrain("bottom", px.Distance, 80, id="length") side.Constrain("leg_front", px.Distance, 60, id="height") side.Constrain("back_end", px.Distance, 8, id="base_t") side.Constrain("leg_top", px.Distance, 8, id="leg_t") print("status", side.status, "| degrees of freedom", side.dof) ``` ```python nbsphinx="hide-input" Row((side, "Every dimension and tie of the profile", {"constraints": True}), caption="The fully constrained profile") ``` ## The rib The rib stands in the inner corner, against the inner faces of the L. `px.Pad` first extrudes the profile 25 to both sides of its plane, 50 wide. `Project` brings the inner faces into the rib's sketch on the same plane. Seen from the side, the base's top and the leg's back are lines, fixed in place, and the fillet is an arc. The projected edges get ids with netgen's edge numbers, such as `inner.Edge.3`, which change when the shape changes (Figure 2): ```python body = px.Pad(side, 25, length2=25) rib = px.Sketch(px.YZ, auto_constraint=True) rib.Project(occ.Compound(list(body.faces["inner"])), construction=False, id="inner") # edges of the sketch, not construction for e in rib.elements["Line"]: print(e, "from", rib.Location(e + ".start"), "to", rib.Location(e + ".end")) ``` ```python nbsphinx="hide-input" Row((rib, "The inner faces projected into the rib's sketch", {"fill": False, "dashed": [side], "view": ((-5, 85), (-5, 65))}), caption="The projected inner faces") ``` The rib's slanted edge starts roughly on the base and runs up to the leg. Its ends are 40 from the leg's back and 40 from the base's top. Trimming the projections where the edge meets them leaves the rib's outline, the edge and the inner faces up to it, the fillet included (Figure 3). ```python rib.Move((50, 10)).Line(until="inner.Edge.11", angle=135, id="rib_edge") rib.Constrain("rib_edge.start", "inner.Edge.3", px.Coincident) rib.Constrain("rib_edge.start", "inner.Edge.11", px.Distance, 40, id="rib_l") rib.Constrain("rib_edge.end", "inner.Edge.3", px.Distance, 40, id="rib_h") rib.Trim(("inner.Edge.3", (75, 8))) # the piece of the base's line nearest to (75, 8), beyond the rib, becomes construction rib.Trim(("inner.Edge.11", (8, 59))) print("status", rib.status, "| degrees of freedom", rib.dof, "| area", round(rib.Face().mass, 2)) ``` ```python nbsphinx="hide-input" Row((rib, "The rib, the projections trimmed to it", {"fill": False, "dashed": [side], "view": ((-5, 85), (-5, 65))}), caption="The rib") ``` ## The solid The rib is padded 3 to both sides of its plane, 6 thick, so that it stands in the middle of the bracket, and `+` fuses it with the L (Figure 4). ```python block = body + px.Pad(rib, 3, length2=3) # + fuses two solids print("solids", len(block.solids), "| volume", round(block.mass, 1)) ``` ```python nbsphinx="hide-input" Views(("The L padded 50 wide, the rib 6 thick in its middle", [(block, "shade"), (block, "ink")]), angles=(-60, 0, -150), caption="The solid") ``` ## The holes The holes in the base, 9 across, are sketched on the bottom of the bracket, its face lowest in $z$. They are placed roughly and set by their spacing of 30 and their distance of 55 from the sketch's horizontal axis, the front edge. `until=px.last` cuts them up through the base (Figure 5). ```python base_holes = px.Sketch(block.faces.Min(occ.Z)) base_holes.Circle(9 / 2, center=(-10, -50), id="left_base_hole") base_holes.Circle(9 / 2, center=(10, -50), id="right_base_hole") base_holes.Constrain("left_base_hole", px.Diameter, 9, id="base_hole_d") base_holes.Constrain("left_base_hole", "right_base_hole", px.Equal) base_holes.Constrain("left_base_hole.center", "right_base_hole.center", px.Distance, 30, id="base_spacing") base_holes.Constrain("left_base_hole.center", "H", px.Distance, 55, id="base_hole_y") base_holes.Constrain("left_base_hole.center", "right_base_hole.center", "V", px.Symmetric) block = px.Pocket(block, base_holes, until=px.last) # through to the last flat face it crosses ``` ```python nbsphinx="hide-input" Views(("Two holes through the base", [(block, "shade"), (block, "ink")]), angles=(-60, 0, -150), caption="The holes in the base") ``` The holes in the leg are sketched on its front, the face lowest in $y$, in the same way, 40 above the bottom edge (Figure 6): ```python leg_holes = px.Sketch(block.faces.Min(occ.Y)) # on the front face, x to the right and z up leg_holes.Circle(9 / 2, center=(-10, 35), id="left_leg_hole") leg_holes.Circle(9 / 2, center=(10, 35), id="right_leg_hole") leg_holes.Constrain("left_leg_hole", px.Diameter, 9, id="leg_hole_d") leg_holes.Constrain("left_leg_hole", "right_leg_hole", px.Equal) leg_holes.Constrain("left_leg_hole.center", "right_leg_hole.center", px.Distance, 30, id="leg_spacing") leg_holes.Constrain("left_leg_hole.center", "H", px.Distance, 40, id="leg_hole_z") leg_holes.Constrain("left_leg_hole.center", "right_leg_hole.center", "V", px.Symmetric) block = px.Pocket(block, leg_holes, until=px.last) ``` ```python nbsphinx="hide-input" Views(("Two holes through the leg", [(block, "shade"), (block, "ink")]), angles=(-60, 0, 30), caption="The holes in the leg") ``` ## 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 front corner in the middle of the width, to compare with your own model. The bracket is symmetric about the plane $x = 0$, so its center of mass lies on it: ```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.y, 2), "back,", round(c.z, 2), "up, off the middle by", round(abs(c.x), 6)) ``` The bracket as STEP, for another CAD system: ```python block.WriteStep("angle_bracket.step") ``` The bracket as STEP: [angle_bracket.step](angle_bracket.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 front, the top, the side seen from the left x0, x1 = block.faces.Min(occ.X).center.x, block.faces.Max(occ.X).center.x height, length = side.Location("leg_front.start")[1], side.Location("bottom.end")[0] base_t, leg_t = side.Location("back_end.end")[1], side.Location("leg_top.start")[0] (rib_y, _), rib_top = rib.Location("rib_edge.start"), rib.Location("rib_edge.end")[1] rib_x = 3 base = [tuple(base_holes.Measure(h)["center"][:2]) for h in ("left_base_hole", "right_base_hole")] leg = [(leg_holes.Measure(h)["center"][0], leg_holes.Measure(h)["center"][2]) for h in ("left_leg_hole", "right_leg_hole")] r = base_holes.Measure("left_base_hole")["radius"] def circle(c, radius): t = [2 * math.pi * k / 96 for k in range(97)] return [(c[0] + radius * math.cos(a), c[1] + radius * math.sin(a)) for a in t] front_view = View( [(leg_holes, {"leg_spacing": -15, "leg_hole_z": x0 - 15, "leg_hole_d": (135, "leader", 2)}, {"left_leg_hole": "lines", "right_leg_hole": "lines"}, px.XZ)], lines=[[(x0, 0), (x1, 0), (x1, height), (x0, height), (x0, 0)]], hidden=[[(x + s * r, 0), (x + s * r, base_t)] for x, _ in base for s in (-1, 1)] + [[(s * rib_x, base_t), (s * rib_x, rib_top)] for s in (-1, 1)], axes=[((0, -5), (0, height + 5))] + [((x, -3), (x, base_t + 3)) for x, _ in base], measures=[((x0, height), (x1, height), height + 12, x1 - x0)]) side_view = View( [(side, {"length": -15, "height": -15, "base_t": length + 15, "leg_t": height + 12, "corner.r": (225, "inside")}, {}, px.YZ)], at=(x1 + 45 + length, 0), mirror=True, lines=[[(rib_y, base_t), (leg_t, rib_top)]], hidden=[[(y + s * r, 0), (y + s * r, base_t)] for _, y in base[:1] for s in (-1, 1)] + [[(0, z + s * r), (leg_t, z + s * r)] for _, z in leg[:1] for s in (-1, 1)], axes=[((base[0][1], -3), (base[0][1], base_t + 3)), ((-3, leg[0][1]), (leg_t + 3, leg[0][1]))], measures=[((leg_t, base_t), (rib_y, base_t), -30, rib.dimensions["rib_l"]), ((leg_t, base_t), (leg_t, rib_top), -30, rib.dimensions["rib_h"])]) top_view = View( [], at=(0, -length - 50), lines=[[(x0, 0), (x1, 0), (x1, length), (x0, length), (x0, 0)], [(x0, leg_t), (x1, leg_t)]] + [[(s * rib_x, leg_t), (s * rib_x, rib_y)] for s in (-1, 1)] + [circle(c, r) for c in base], hidden=[[(x + s * r, 0), (x + s * r, leg_t)] for x, _ in leg for s in (-1, 1)], circles=[(c, r, "lines") for c in base], axes=[((0, -5), (0, length + 5))] + [((x, -3), (x, leg_t + 3)) for x, _ in leg], diameters=[(base[0], r, (135, "leader", 2))], measures=[(base[0], base[1], length + 12, base[1][0] - base[0][0]), ((base[1][0], 0), base[1], x1 + 15, base[1][1]), ((-rib_x, leg_t), (rib_x, leg_t), -28, 2 * rib_x)]) drawing = SheetFigure([front_view, side_view, top_view, Pictorial(block, at=(x1 + 45 + length / 2, -length / 2 - 50), width=80)], margin=30, notes=("planex2d.org", "3D exercise 03"), 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, "angle-bracket", dpi=200) plt.close(drawing) ```