--- jupyter: description: 'A hydraulic manifold block as a 3D CAD exercise: a dimensioned drawing with a section through its channels to model in planex2d or any CAD system, a solution with ports sketched on three faces and placed from their projected edges, drilled channels that meet inside, and the passage, the volume, mass and center of mass to check a model against.' --- # Manifold block A manifold block joins the lines of a hydraulic circuit without pipes. Its ports take the fittings, and channels drilled from them meet inside the block. Each port lies on its own face and is dimensioned from that face's edges, and every channel is drilled from its port, so the block is three sketches on three faces whose holes are pocketed into the same solid until they meet. Model the block from the drawing in planex2d or in any CAD system. Then check that its channels form one passage, and compare its volume, mass and center of mass with the values at the end of the page. A solution in planex2d follows. ![The manifold block exercise: a dimensioned drawing of a block with ports on its front, top and right faces and channels that meet inside, in a front view, a section through the channels and an isometric](../_static/gallery/manifold-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 block The block is 70 long, 50 deep and 40 high, a `Rectangle` on the xy plane from the origin, its corner at the bottom left front, padded 40 up. With `auto_constraint={"horizontal": True, "vertical": True}`, its edges, drawn level and plumb, stay so (Figure 1): ```python base = px.Sketch(auto_constraint={"horizontal": True, "vertical": True}) base.Rectangle(70, 50, id="base") base.Constrain("base.Line.1.start", "origin", px.Coincident) base.Constrain("base.Line.1", px.Distance, 70, id="length") base.Constrain("base.Line.2", px.Distance, 50, id="depth") print("status", base.status, "| degrees of freedom", base.dof) block = px.Pad(base, 40) ``` ```python nbsphinx="hide-input" Views(("The block", [(block, "shade"), (block, "ink")]), angles=(-60, 0, 30), caption="The block") ``` ## The ports A port is a spot face of 14, 2 deep, for the fitting's seal, and a channel of 8 drilled from it. `port` sketches the spot face on a face of the block, places it from two of the face's edges, projected into the sketch, and pockets it. The channel is a second sketch on the same face around the spot face's solved center, pocketed to its depth. The channels end flat, as modeled, where a drill would leave a cone. ```python def port(block, face, edge_a, a, edge_b, b, depth): """block with a port on face: its center a from edge_a and b from edge_b, the channel depth deep""" spot = px.Sketch(face) spot.Project(edge_a, id="edge_a") spot.Project(edge_b, id="edge_b") spot.Circle(14 / 2, center=spot.ToHV(tuple(face.center)), id="spot") # placed roughly, at the face's middle spot.Constrain("spot", px.Diameter, 14, id="spot_d") spot.Constrain("spot.center", "edge_a.Edge.0", px.Distance, a, id="from_a") spot.Constrain("spot.center", "edge_b.Edge.0", px.Distance, b, id="from_b") block = px.Pocket(block, spot, 2) channel = px.Sketch(face) channel.Circle(8 / 2, center=spot.Location("spot.center"), id="channel") channel.Constrain("channel", px.Diameter, 8, id="channel_d") return px.Pocket(block, channel, depth), spot ``` The three ports, named as the lines of the circuit: - P on the front, the face lowest in $y$, 15 from its left edge and 20 above its bottom edge, drilled 34 deep; - A on the top, the face highest in $z$, 15 from its left edge and 30 behind its front edge, drilled 24 deep; - B on the right, the face highest in $x$, 30 behind its front edge and 20 above its bottom edge, drilled 59 deep. The channels meet at the point (15, 30, 20), and each runs 4 past it, the channel's radius, so that the three open fully into each other (Figure 2): ```python front = block.faces.Min(occ.Y) block, p = port(block, front, front.edges.Min(occ.X), 15, front.edges.Min(occ.Z), 20, 34) top = block.faces.Max(occ.Z) block, a = port(block, top, top.edges.Min(occ.X), 15, top.edges.Min(occ.Y), 30, 24) right = block.faces.Max(occ.X) block, b = port(block, right, right.edges.Min(occ.Y), 30, right.edges.Min(occ.Z), 20, 59) for name, sk in (("P", p), ("A", a), ("B", b)): print(name, "status", sk.status, "| center", tuple(round(v, 6) for v in sk.Measure("spot")["center"])) ``` ```python nbsphinx="hide-input" Row((p, "Port P on the front, placed from its edges", {"plot": {"show_parent": block, "show_constraints": True}}), (block, "The three ports", {"views": [(block, "shade"), (block, "ink")], "angles": (-60, 0, 30)}), caption="The ports") ``` ## The mounting holes The block is bolted down through two holes of 6.6, both 50 from the left edge of the top, 10 and 42 behind its front edge, clear of channel B by 12. `until=px.last` cuts them down through the block (Figure 3): ```python top = block.faces.Max(occ.Z) holes = px.Sketch(top) holes.Project(top.edges.Min(occ.X), id="left_edge") holes.Project(top.edges.Min(occ.Y), id="front_edge") holes.Circle(6.6 / 2, center=(48, 12), id="front_hole") holes.Circle(6.6 / 2, center=(48, 40), id="back_hole") holes.Constrain("front_hole", px.Diameter, 6.6, id="hole_d") holes.Constrain("back_hole", "front_hole", px.Equal) holes.Constrain("front_hole.center", "left_edge.Edge.0", px.Distance, 50, id="hole_x") holes.Constrain("back_hole.center", "left_edge.Edge.0", px.Distance, 50) holes.Constrain("front_hole.center", "front_edge.Edge.0", px.Distance, 10, id="front_y") holes.Constrain("back_hole.center", "front_edge.Edge.0", px.Distance, 42, id="back_y") print("status", holes.status, "| degrees of freedom", holes.dof) block = px.Pocket(block, holes, until=px.last) # through to the last flat face it crosses ``` ```python nbsphinx="hide-input" Views(("The mounting holes", [(block, "shade"), (block, "ink")]), angles=(-60, 0, 30), caption="The mounting holes") ``` ## The checks The passage is the space taken out of the block, the box less the block. Its pieces are the solids of that difference, and the three ports' channels must form one of them, with the two mounting holes apart. Then come the volume, the mass in aluminum EN AW-6082 (2.70 g/cm³) and the center of mass, with the origin at the bottom left front corner, to compare with your own model: ```python taken = occ.Box(occ.Pnt(0, 0, 0), occ.Pnt(70, 50, 40)) - block # everything drilled out of the box pieces = sorted(taken.solids, key=lambda s: -s.mass) print(len(pieces), "pieces taken out: the passage", round(pieces[0].mass, 1), "mm³ and", len(pieces) - 1, "mounting holes") c = block.center print("volume", round(block.mass, 1), "mm³") print("mass", round(block.mass * 2.70e-6, 4), "kg") print("center of mass", tuple(round(v, 3) for v in (c.x, c.y, c.z))) ``` The block as STEP, for another CAD system: ```python block.WriteStep("manifold_block.step") ``` The block as STEP: [manifold_block.step](manifold_block.step) ```python nbsphinx="hidden" import sys sys.path.insert(0, "..") import matplotlib.pyplot as plt from drawings import LANDSCAPE, Pictorial, SheetFigure, View from gallery import save_figure # first angle: the front view with the cutting plane A-A at the channels' height; below it the section A-A, the part # below the plane z = 20 seen from above, its cut faces hatched; the view from the right to the left of the front view view_front = px.XZ # x to the right, z up front_view = View([(p, {"from_a": 48, "from_b": -8, "spot_d": (315, "leader")}, {}, view_front)], solid=block, plane=view_front, hidden_edges=True, axes=[((-6, 20), (76, 20))], arrows=[((-6, 20), (0, -1), "A"), ((76, 20), (0, -1), "A")], measures=[((0, 0), (70, 0), -8, 70), ((70, 0), (70, 40), 84, 40)]) below = block * occ.Box((-1, -1, -1), (71, 51, 20)) cut = [f for f in below.faces if abs(f.center.z - 20) < 1e-6 and f.bounding_box[1].z - f.bounding_box[0].z < 1e-6] section = View([(a, {"from_a": -8, "from_b": 80}, {}), (holes, {"hole_x": -16, "front_y": 78, "back_y": 86, "hole_d": (300, "leader", 2)})], solid=below, plane=px.XY, hidden_edges=False, hatch=cut, at=(0, -73), label="A-A", measures=[((0, 0), (0, 50), -10, 50)]) view_right = px.Plane(normal=(1, 0, 0)) # the view from the right: y to the right, z up right_view = View([(b, {"from_a": -8, "from_b": -8}, {}, view_right)], solid=block, plane=view_right, at=(-76, 0), hidden_edges=False) pictorial = Pictorial(block, at=(-52, -64), width=50, angles=(-60, 0, 30)) drawing = SheetFigure([front_view, section, right_view, pictorial], margin=20, notes=("planex2d.org", "3D exercise 24"), first_angle=True, sheet=LANDSCAPE) print("scale", drawing.planex_scale, "| paper", [round(v * 25.4) for v in drawing.get_size_inches()], "mm") save_figure(drawing, "manifold-block", dpi=200) plt.close(drawing) ```