loadpath 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- loadpath/README.md +294 -0
- loadpath/SKILL.md +584 -0
- loadpath/__init__.py +17 -0
- loadpath/__main__.py +1202 -0
- loadpath/am.py +316 -0
- loadpath/amgfast.py +326 -0
- loadpath/assembly.py +985 -0
- loadpath/benchmarks/__init__.py +0 -0
- loadpath/benchmarks/beams.py +266 -0
- loadpath/benchmarks/cases/ge-bracket.json +380 -0
- loadpath/benchmarks/evaluate.py +172 -0
- loadpath/benchmarks/mechanisms.py +416 -0
- loadpath/benchmarks/problems.py +140 -0
- loadpath/benchmarks/reference/bridge-3d.top3d.npz +0 -0
- loadpath/benchmarks/reference/cantilever-3d.top3d.npz +0 -0
- loadpath/benchmarks/reference/lbracket-2d.top88-ft2.npz +0 -0
- loadpath/benchmarks/reference/mbb-2d.top88-ft1.npz +0 -0
- loadpath/benchmarks/reference/mbb-2d.top88-ft2.npz +0 -0
- loadpath/benchmarks/reference.py +138 -0
- loadpath/benchmarks/run.py +301 -0
- loadpath/brep.py +1470 -0
- loadpath/bucklingopt.py +300 -0
- loadpath/buildsim.py +1490 -0
- loadpath/cadimport.py +904 -0
- loadpath/calibration.py +885 -0
- loadpath/cavities.py +211 -0
- loadpath/checks/__init__.py +0 -0
- loadpath/checks/dfm.py +354 -0
- loadpath/checks/exchanger.py +7 -0
- loadpath/checks/fea.py +212 -0
- loadpath/checks/fit.py +475 -0
- loadpath/checks/networks.py +260 -0
- loadpath/checks/opening.py +67 -0
- loadpath/checks/sdf_kit.py +7 -0
- loadpath/conjugate.py +267 -0
- loadpath/constrained.py +329 -0
- loadpath/contact.py +691 -0
- loadpath/coopt.py +342 -0
- loadpath/cores.py +163 -0
- loadpath/crease.py +1138 -0
- loadpath/critic.py +212 -0
- loadpath/crosscheck.py +1107 -0
- loadpath/cutcell.py +510 -0
- loadpath/data/lattice_buckling.json +439 -0
- loadpath/data/lattice_tensors.json +27449 -0
- loadpath/data/published_experiments.json +71 -0
- loadpath/decimate.py +177 -0
- loadpath/delivery.py +190 -0
- loadpath/directions.py +262 -0
- loadpath/domain.py +630 -0
- loadpath/drains.py +615 -0
- loadpath/durability.py +413 -0
- loadpath/editor.html +3135 -0
- loadpath/editor.py +610 -0
- loadpath/escape.py +354 -0
- loadpath/evals/__init__.py +0 -0
- loadpath/evals/baselines/coarse.json +1543 -0
- loadpath/evals/baselines/full.json +1497 -0
- loadpath/evals/brief_kinds/__init__.py +1 -0
- loadpath/evals/brief_kinds/heatx.py +387 -0
- loadpath/evals/brief_kinds/lattice.py +571 -0
- loadpath/evals/brief_kinds/multimat.py +593 -0
- loadpath/evals/briefs/assets/bracket.step +1201 -0
- loadpath/evals/briefs/assets/desk_arm.urdf +22 -0
- loadpath/evals/briefs/assets/elbow-gearbox.stl +0 -0
- loadpath/evals/briefs/assets/motor-body.stl +0 -0
- loadpath/evals/briefs/assets/sketch.png +0 -0
- loadpath/evals/briefs/base-adapter-fdm.json +492 -0
- loadpath/evals/briefs/bell-crank-lpbf.json +180 -0
- loadpath/evals/briefs/belt-tensioner-fdm.json +151 -0
- loadpath/evals/briefs/camera-boom-mjf.json +339 -0
- loadpath/evals/briefs/camera-mount-pla.json +203 -0
- loadpath/evals/briefs/coolant-isolator-petg.json +100 -0
- loadpath/evals/briefs/elbow-bearing-bracket-sls.json +300 -0
- loadpath/evals/briefs/elbow-gearbox-forearm-mjf.json +558 -0
- loadpath/evals/briefs/elbow-link-fatigue-fdm.json +251 -0
- loadpath/evals/briefs/elbow-stop-pad-tpu.json +39 -0
- loadpath/evals/briefs/forearm-fdm.json +146 -0
- loadpath/evals/briefs/forearm-servo-link-fdm.json +327 -0
- loadpath/evals/briefs/grip-pad-finger-mm.json +354 -0
- loadpath/evals/briefs/gripper-bracket-creep-fdm.json +340 -0
- loadpath/evals/briefs/gripper-finger-fdm.json +155 -0
- loadpath/evals/briefs/gripper-jaw-fatigue-mjf.json +328 -0
- loadpath/evals/briefs/gripper-palm-fdm.json +715 -0
- loadpath/evals/briefs/hot-stepper-mount-asa-fdm.json +471 -0
- loadpath/evals/briefs/hpu-oil-cooler-lpbf.json +121 -0
- loadpath/evals/briefs/isolated-camera-mount-mm.json +675 -0
- loadpath/evals/briefs/kart-charge-cooler-lpbf.json +93 -0
- loadpath/evals/briefs/lighten-step-bracket.json +220 -0
- loadpath/evals/briefs/nema17-mount-stl.json +429 -0
- loadpath/evals/briefs/pillow-block-sls.json +220 -0
- loadpath/evals/briefs/polyjet-fingertip-mm.json +305 -0
- loadpath/evals/briefs/rod-hook-pla.json +154 -0
- loadpath/evals/briefs/sensor-arm-resin.json +150 -0
- loadpath/evals/briefs/shelf-bracket-pla.json +158 -0
- loadpath/evals/briefs/shoulder-riser-mjf.json +487 -0
- loadpath/evals/briefs/sketch-clevis.json +229 -0
- loadpath/evals/briefs/spring-bracket-pacf.json +294 -0
- loadpath/evals/briefs/tool-flange-ti64.json +280 -0
- loadpath/evals/briefs/tray-arm-lattice-fdm.json +260 -0
- loadpath/evals/briefs/turret-fdm.json +281 -0
- loadpath/evals/briefs/upper-arm-sls.json +179 -0
- loadpath/evals/briefs/urdf-forearm.json +233 -0
- loadpath/evals/briefs/wrist-link-mjf.json +278 -0
- loadpath/evals/briefs/wrist-yoke-mjf.json +245 -0
- loadpath/evals/briefs.py +393 -0
- loadpath/evals/cases/arm-link-cast.json +157 -0
- loadpath/evals/cases/arm-link-driven.json +171 -0
- loadpath/evals/cases/arm-link-sandwich.json +315 -0
- loadpath/evals/cases/arm-link.json +111 -0
- loadpath/evals/cases/bell-crank.json +116 -0
- loadpath/evals/cases/grip-turret.json +479 -0
- loadpath/evals/cases/grip-upper-arm.json +253 -0
- loadpath/evals/cases/grip-wrist-link-mjf.json +929 -0
- loadpath/evals/cases/grip-wrist-yoke.json +831 -0
- loadpath/evals/cases/lpbf-intercooler-core.json +59 -0
- loadpath/evals/cases/mbb-beam.json +61 -0
- loadpath/evals/cases/motor-body.stl +0 -0
- loadpath/evals/cases/motor-mount.json +265 -0
- loadpath/evals/cases/printed-hook.json +77 -0
- loadpath/evals/cases/wall-bracket-fasteners.json +125 -0
- loadpath/evals/cases/wall-bracket.json +126 -0
- loadpath/evals/corpus.py +179 -0
- loadpath/evals/pareto.py +90 -0
- loadpath/evals/run.py +860 -0
- loadpath/examples/arm-link.json +25 -0
- loadpath/examples/bumpered-bracket-compression.json +225 -0
- loadpath/examples/bumpered-bracket-lattice.json +204 -0
- loadpath/examples/bumpered-bracket.json +34 -0
- loadpath/examples/gripper-finger-mm.json +185 -0
- loadpath/examples/heat-exchanger-counterflow.json +21 -0
- loadpath/examples/heat-exchanger-crossflow-tuned.json +48 -0
- loadpath/examples/heat-exchanger-crossflow.json +20 -0
- loadpath/examples/pedal-lattice.json +23 -0
- loadpath/examples/pressure-plenum.json +12 -0
- loadpath/examples/shelf-bracket.json +27 -0
- loadpath/exchanger.py +831 -0
- loadpath/fasteners.py +199 -0
- loadpath/fem.py +668 -0
- loadpath/femops.py +70 -0
- loadpath/fieldjit.py +981 -0
- loadpath/fieldops.py +119 -0
- loadpath/fields.py +3697 -0
- loadpath/fieldtorch.py +464 -0
- loadpath/flow.py +984 -0
- loadpath/flowtopo.py +300 -0
- loadpath/gpu.py +521 -0
- loadpath/heatx.py +1067 -0
- loadpath/heatx_thermal.py +432 -0
- loadpath/homogenized.py +886 -0
- loadpath/ibrep.py +552 -0
- loadpath/impact.py +693 -0
- loadpath/infill.py +376 -0
- loadpath/interface_library.py +340 -0
- loadpath/interlock.py +186 -0
- loadpath/isolator.py +149 -0
- loadpath/knn.py +204 -0
- loadpath/lattice.py +1065 -0
- loadpath/lattice3mf.py +760 -0
- loadpath/lattice_buckling.py +245 -0
- loadpath/lattice_grade.py +325 -0
- loadpath/lattice_orient.py +595 -0
- loadpath/lattice_print.py +159 -0
- loadpath/lattice_tensors.py +558 -0
- loadpath/latticestep.py +1210 -0
- loadpath/manufacture.py +446 -0
- loadpath/material_library.py +489 -0
- loadpath/materials.py +144 -0
- loadpath/mcp_server.py +786 -0
- loadpath/mechanism.py +218 -0
- loadpath/mechanism_nl.py +811 -0
- loadpath/meshops.py +452 -0
- loadpath/mg.py +853 -0
- loadpath/mma.py +214 -0
- loadpath/modal.py +210 -0
- loadpath/multimat.py +1788 -0
- loadpath/narrowband.py +418 -0
- loadpath/nlkernel.py +241 -0
- loadpath/nonlinear.py +1095 -0
- loadpath/orient.py +334 -0
- loadpath/porous.py +994 -0
- loadpath/pressure.py +171 -0
- loadpath/preview.py +335 -0
- loadpath/printcheck.py +263 -0
- loadpath/printsetup.py +207 -0
- loadpath/processcheck.py +376 -0
- loadpath/quick.py +113 -0
- loadpath/recall.py +406 -0
- loadpath/reconstruct.py +797 -0
- loadpath/render.py +436 -0
- loadpath/resolved.py +189 -0
- loadpath/schema.py +1701 -0
- loadpath/sdf_kit.py +843 -0
- loadpath/selfx.py +292 -0
- loadpath/shapes.py +584 -0
- loadpath/sharp.py +717 -0
- loadpath/simp.py +1170 -0
- loadpath/sizing.py +312 -0
- loadpath/slicer.py +321 -0
- loadpath/spec.py +1072 -0
- loadpath/step.py +806 -0
- loadpath/struts.py +639 -0
- loadpath/study.py +1937 -0
- loadpath/tasks.py +822 -0
- loadpath/thermal.py +1691 -0
- loadpath/threemf.py +487 -0
- loadpath/tune.py +437 -0
- loadpath/validation.py +145 -0
- loadpath/verify.py +1215 -0
- loadpath/viewer.html +694 -0
- loadpath/viewer.py +446 -0
- loadpath/viscoelastic.py +106 -0
- loadpath/voxelprint.py +582 -0
- loadpath/voxels.py +402 -0
- loadpath-0.1.1.dist-info/METADATA +319 -0
- loadpath-0.1.1.dist-info/RECORD +220 -0
- loadpath-0.1.1.dist-info/WHEEL +5 -0
- loadpath-0.1.1.dist-info/entry_points.txt +2 -0
- loadpath-0.1.1.dist-info/licenses/LICENSE +21 -0
- loadpath-0.1.1.dist-info/top_level.txt +1 -0
loadpath/fasteners.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Fasteners as interfaces, not bosses.
|
|
3
|
+
|
|
4
|
+
A bolted joint needs very little of the part to be fixed: a flat ring for
|
|
5
|
+
the head or washer to bear on, a flat ring where it clamps against the
|
|
6
|
+
mating part (or a sleeve of material for the thread, an insert or a nut), a
|
|
7
|
+
clear hole, and a clear path for the head and the driver. Drawing a solid
|
|
8
|
+
boss round all of that (the old habit) preserves material the joint does not
|
|
9
|
+
need; drawing only the hole loses the clamp. This expands a `fasteners`
|
|
10
|
+
entry into exactly those preserve and keep-out regions, plus a self-balanced
|
|
11
|
+
preload case that squeezes the seat towards the far end, so the optimizer
|
|
12
|
+
grows its own compression cone between them instead of inheriting a solid
|
|
13
|
+
cylinder.
|
|
14
|
+
|
|
15
|
+
"fasteners": [{
|
|
16
|
+
"id": "top", "size": "M5",
|
|
17
|
+
"seat": [6, 0, -20], # centre of the face the head bears on
|
|
18
|
+
"direction": "-x", # from the seat into the part, the way the screw goes
|
|
19
|
+
"grip": 6, # mm from the seat to the far end
|
|
20
|
+
"end": "face", # "face" (clamps on a mating part), "thread", or "none"
|
|
21
|
+
"headOn": "this", # "mating": the head is on the mating part, which bears on
|
|
22
|
+
# this part at the seat (no head or driver path to keep clear)
|
|
23
|
+
"threadLength": 8, # end = "thread": engagement length past the grip
|
|
24
|
+
"insert": 6.4, # optional: heat-set insert or nut OD; else a tapped hole
|
|
25
|
+
"preload": 600 # N; default from the seat's bearing area
|
|
26
|
+
}]
|
|
27
|
+
|
|
28
|
+
A screw driven from the mating part into this one is `"headOn": "mating"`,
|
|
29
|
+
`"end": "thread"`: the seat is then where the mating part clamps down, and
|
|
30
|
+
only the bore and the tap are kept clear.
|
|
31
|
+
|
|
32
|
+
Regions it adds, by id: `<id>-bore`, `<id>-head` (unless the head is on
|
|
33
|
+
the other part; and `<id>-tap` for a thread) as keep-outs; `<id>-seat` and `<id>-face` or `<id>-thread` as
|
|
34
|
+
preserves. Supports and loads can name any of them. Hold a bore across
|
|
35
|
+
the screw only (`"fix": "yz"` for a screw along x): fixed along it too,
|
|
36
|
+
the bore would hold the preload ring and the preload would do nothing.
|
|
37
|
+
"""
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
import copy
|
|
41
|
+
|
|
42
|
+
import numpy as np
|
|
43
|
+
|
|
44
|
+
__all__ = ["expand_fasteners", "METRIC"]
|
|
45
|
+
|
|
46
|
+
# ISO metric: (medium clearance hole, ISO 4762 socket head diameter), mm.
|
|
47
|
+
# The heads were a mix of pan, socket and washer sizes (M4 8.5 mm): a brief
|
|
48
|
+
# with 8 mm M4 counterbores read 678 mm^3 of keep-out intrusion against the
|
|
49
|
+
# default's 10.5 mm head keep-out (2026-11-01). Give `head` for a washer or a
|
|
50
|
+
# pan head, `counterbore` for the pocket the head sits in.
|
|
51
|
+
METRIC = {"M2": (2.4, 3.8), "M2.5": (2.9, 4.5), "M3": (3.4, 5.5), "M4": (4.5, 7.0),
|
|
52
|
+
"M5": (5.5, 8.5), "M6": (6.6, 10.0), "M8": (9.0, 13.0), "M10": (11.0, 16.0)}
|
|
53
|
+
_AXES = {"x": (1, 0, 0), "y": (0, 1, 0), "z": (0, 0, 1)}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _direction(v, label):
|
|
57
|
+
if isinstance(v, str):
|
|
58
|
+
s = v.strip().lower()
|
|
59
|
+
sign = -1.0 if s.startswith("-") else 1.0
|
|
60
|
+
ax = s.lstrip("+-")
|
|
61
|
+
if ax not in _AXES:
|
|
62
|
+
raise ValueError(f"{label}.direction must be like '-x' or a vector, got {v!r}")
|
|
63
|
+
return sign * np.asarray(_AXES[ax], float)
|
|
64
|
+
a = np.asarray(v, float).reshape(-1)
|
|
65
|
+
if a.shape != (3,) or not np.linalg.norm(a) > 0:
|
|
66
|
+
raise ValueError(f"{label}.direction must be like '-x' or a non-zero vector")
|
|
67
|
+
return a / np.linalg.norm(a)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _tube(p0, p1, r, r_inner=0.0):
|
|
71
|
+
out = {"type": "cylinder", "p0": np.round(p0, 4).tolist(), "p1": np.round(p1, 4).tolist(), "r": float(r)}
|
|
72
|
+
if r_inner:
|
|
73
|
+
out["rInner"] = float(r_inner)
|
|
74
|
+
return out
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _exit_distance(d: dict, p, direction) -> float:
|
|
78
|
+
"""How far from p along `direction` the ray leaves the bounding box of
|
|
79
|
+
the design space's boxes (0 when it has none)."""
|
|
80
|
+
boxes = [s for s in d.get("designSpace") or [] if isinstance(s, dict) and s.get("type") == "box"]
|
|
81
|
+
if not boxes:
|
|
82
|
+
return 0.0
|
|
83
|
+
lo = np.min([b["min"] for b in boxes], axis=0)
|
|
84
|
+
hi = np.max([b["max"] for b in boxes], axis=0)
|
|
85
|
+
p, u = np.asarray(p, float), np.asarray(direction, float)
|
|
86
|
+
t = np.inf
|
|
87
|
+
for k in range(3):
|
|
88
|
+
if u[k] > 1e-12:
|
|
89
|
+
t = min(t, (hi[k] - p[k]) / u[k])
|
|
90
|
+
elif u[k] < -1e-12:
|
|
91
|
+
t = min(t, (lo[k] - p[k]) / u[k])
|
|
92
|
+
return float(max(t, 0.0)) if np.isfinite(t) else 0.0
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def expand_fasteners(d: dict) -> dict:
|
|
96
|
+
"""The study with each `fasteners` entry turned into regions and a
|
|
97
|
+
preload case. Returns d itself when there are none."""
|
|
98
|
+
fs = d.get("fasteners")
|
|
99
|
+
if not fs:
|
|
100
|
+
return d
|
|
101
|
+
from .materials import material_from_spec
|
|
102
|
+
|
|
103
|
+
d = copy.deepcopy(d)
|
|
104
|
+
preserve = list(d.get("preserve") or [])
|
|
105
|
+
keep_out = list(d.get("keepOut") or [])
|
|
106
|
+
mat_in = d.get("material", "pla")
|
|
107
|
+
mat = material_from_spec(mat_in[0] if isinstance(mat_in, list) else mat_in)
|
|
108
|
+
preload_loads = []
|
|
109
|
+
joints = []
|
|
110
|
+
for i, f in enumerate(fs):
|
|
111
|
+
label = f"fasteners[{i}]"
|
|
112
|
+
fid = f.get("id") or f"fastener-{i}"
|
|
113
|
+
size = f.get("size")
|
|
114
|
+
if size is not None and size not in METRIC:
|
|
115
|
+
raise ValueError(f"{label}.size must be one of {sorted(METRIC)} (or give diameter and head)")
|
|
116
|
+
hole, head = METRIC[size] if size else (None, None)
|
|
117
|
+
hole = float(f.get("hole", hole or 0.0)) or (1.1 * float(f["diameter"]) if "diameter" in f else None)
|
|
118
|
+
head = float(f.get("head", head or 0.0)) or None
|
|
119
|
+
if not hole or not head:
|
|
120
|
+
raise ValueError(f"{label} needs a size (e.g. 'M5') or diameter and head (mm)")
|
|
121
|
+
nominal = float(f.get("diameter", hole / 1.1))
|
|
122
|
+
if "seat" not in f or "direction" not in f or "grip" not in f:
|
|
123
|
+
raise ValueError(f"{label} needs seat (point), direction (into the part) and grip (mm)")
|
|
124
|
+
seat = np.asarray(f["seat"], float)
|
|
125
|
+
a = _direction(f["direction"], label)
|
|
126
|
+
grip = float(f["grip"])
|
|
127
|
+
if grip < 0 or (grip == 0 and str(f.get("end", "face")) != "thread"):
|
|
128
|
+
raise ValueError(f"{label}.grip is mm from the seat to the far end: > 0, or 0 for a thread or "
|
|
129
|
+
"insert that starts right at the seat")
|
|
130
|
+
end_kind = str(f.get("end", "face"))
|
|
131
|
+
if end_kind not in ("face", "thread", "none"):
|
|
132
|
+
raise ValueError(f"{label}.end must be 'face', 'thread' or 'none'")
|
|
133
|
+
head_on = str(f.get("headOn", "this"))
|
|
134
|
+
if head_on not in ("this", "mating"):
|
|
135
|
+
raise ValueError(f"{label}.headOn must be 'this' or 'mating'")
|
|
136
|
+
# the rings: a little wider than the head, a couple of layers thick;
|
|
137
|
+
# the thread sleeve's wall about a third of the screw's diameter.
|
|
138
|
+
# (The optimizer adds what the loads need around them.)
|
|
139
|
+
wall = float(f.get("wall", max(1.0, 0.15 * nominal)))
|
|
140
|
+
ring = float(f.get("ring", max(2.0, 0.4 * nominal)))
|
|
141
|
+
sleeve = float(f.get("sleeveWall", max(1.5, 0.35 * nominal)))
|
|
142
|
+
# The driver comes in from outside the part, so by default its path
|
|
143
|
+
# runs on until it leaves the design space: a fixed 60 mm path ended
|
|
144
|
+
# inside the wall bracket's 120 mm arm, the optimizer built on along
|
|
145
|
+
# the driver's line past it, and verify read the bore closed there
|
|
146
|
+
# (wall-bracket-fasteners, every fraction from 4% to 19%).
|
|
147
|
+
driver = float(f.get("driver", max(60.0, _exit_distance(d, seat, -a) + 2.0)))
|
|
148
|
+
end = seat + a * grip
|
|
149
|
+
r_ring = 0.5 * head + wall
|
|
150
|
+
keep_out.append({"id": f"{fid}-bore", "shape": _tube(seat - a * 1.0, end + a * 2.0, 0.5 * hole)})
|
|
151
|
+
if head_on == "this":
|
|
152
|
+
# as wide as the seat ring: a head path narrower than the ring
|
|
153
|
+
# leaves a sub-millimetre lip of ring standing round it
|
|
154
|
+
cb = f.get("counterbore")
|
|
155
|
+
if cb is not None and float(cb) <= head:
|
|
156
|
+
raise ValueError(f"{label}.counterbore ({float(cb):g} mm) must be wider than the head ({head:g} mm)")
|
|
157
|
+
r_head = 0.5 * float(cb) if cb is not None else max(0.5 * head + 0.5, r_ring)
|
|
158
|
+
keep_out.append({"id": f"{fid}-head", "shape": _tube(seat, seat - a * driver, r_head)})
|
|
159
|
+
preserve.append({"id": f"{fid}-seat", "shape": _tube(seat, seat + a * ring, r_ring, 0.5 * hole)})
|
|
160
|
+
far = None
|
|
161
|
+
if end_kind == "face":
|
|
162
|
+
far = f"{fid}-face"
|
|
163
|
+
preserve.append({"id": far, "shape": _tube(end - a * ring, end, r_ring, 0.5 * hole)})
|
|
164
|
+
elif end_kind == "thread":
|
|
165
|
+
tl = float(f.get("threadLength", 2.0 * nominal))
|
|
166
|
+
insert = f.get("insert")
|
|
167
|
+
r_hole = 0.5 * float(insert) if insert else 0.42 * nominal # insert bore, or tap drill
|
|
168
|
+
r_sleeve = (0.5 * float(insert) if insert else 0.5 * nominal) + sleeve
|
|
169
|
+
far = f"{fid}-thread"
|
|
170
|
+
keep_out.append({"id": f"{fid}-tap", "shape": _tube(end - a * 0.5, end + a * (tl + 1.0), r_hole)})
|
|
171
|
+
preserve.append({"id": far, "shape": _tube(end, end + a * tl, r_sleeve, r_hole)})
|
|
172
|
+
if far is not None:
|
|
173
|
+
if "preload" in f:
|
|
174
|
+
p = float(f["preload"])
|
|
175
|
+
else:
|
|
176
|
+
# a quarter of what the seat can bear: plastics creep under
|
|
177
|
+
# sustained bearing stress
|
|
178
|
+
area = 0.25 * np.pi * (head ** 2 - hole ** 2)
|
|
179
|
+
p = 0.25 * mat.strength * area
|
|
180
|
+
# ...and no more than a class 8.8 screw is tightened to (about
|
|
181
|
+
# 60% of its proof load on the stress area): a titanium seat
|
|
182
|
+
# read 10 kN for an M4, three times what the screw carries
|
|
183
|
+
screw = 0.6 * 600.0 * 0.25 * np.pi * (0.83 * nominal) ** 2
|
|
184
|
+
p = min(p, screw)
|
|
185
|
+
if p > 0:
|
|
186
|
+
joints.append({"id": fid, "preload": p, "area": 0.25 * np.pi * (head ** 2 - hole ** 2),
|
|
187
|
+
"axis": a.tolist()})
|
|
188
|
+
preload_loads.append({"region": f"{fid}-seat", "force": (a * p).tolist()})
|
|
189
|
+
preload_loads.append({"region": far, "force": (-a * p).tolist()})
|
|
190
|
+
d["preserve"] = preserve
|
|
191
|
+
d["keepOut"] = keep_out
|
|
192
|
+
if preload_loads:
|
|
193
|
+
cases = list(d.get("loadCases") or [])
|
|
194
|
+
cases.append({"name": "preload", "loads": preload_loads})
|
|
195
|
+
d["loadCases"] = cases
|
|
196
|
+
d.pop("fasteners")
|
|
197
|
+
# for the durability check's preload relaxation (spec pops it)
|
|
198
|
+
d["_joints"] = joints
|
|
199
|
+
return d
|