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.

[1]:
import math
import netgen.occ as occ
import planex as px
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):
[3]:
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)
status full | degrees of freedom 0
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.
[5]:
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):
[6]:
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"]))
P status full | center (15.0, 0.0, 20.0)
A status full | center (15.0, 30.0, 40.0)
B status full | center (70.0, 30.0, 20.0)
(a) Port P on the front, placed from its edges
(b) The three 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):
[8]:
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
status full | degrees of freedom 0
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:
[10]:
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)))
3 pieces taken out: the passage 5779.0 mm³ and 2 mounting holes
volume 131484.0 mm³
mass 0.355 kg
center of mass (34.906, 24.974, 19.897)
The block as STEP, for another CAD system:
[11]:
block.WriteStep("manifold_block.step")
The block as STEP: manifold_block.step