dsh-engineering-ui 0.0.0-stage → 1.5.1
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.
- package/LICENSE +21 -0
- package/README.md +772 -3
- package/engineering/plugins/dsh-engineering-ui/cordis.patch.yml +5 -0
- package/engineering/plugins/dsh-engineering-ui/icon.svg +14 -0
- package/engineering/plugins/dsh-engineering-ui/lib/client.js +8257 -0
- package/engineering/plugins/dsh-engineering-ui/lib/defense-sign.js +1552 -0
- package/engineering/plugins/dsh-engineering-ui/lib/index.js +5634 -0
- package/engineering/plugins/dsh-engineering-ui/lib/job-wait-guard.js +236 -0
- package/engineering/plugins/dsh-engineering-ui/lib/mem-guard.js +331 -0
- package/engineering/plugins/dsh-engineering-ui/lib/webots.js +114 -0
- package/engineering/plugins/dsh-engineering-ui/locale/en.json +6 -0
- package/engineering/plugins/dsh-engineering-ui/locale/zh.json +6 -0
- package/engineering/plugins/dsh-engineering-ui/preset-engineering.patch.yml +601 -0
- package/engineering/skills/assembly-orchestration/SKILL.md +651 -0
- package/engineering/skills/cad-workflow/SKILL.md +132 -0
- package/engineering/skills/mode-selection/SKILL.md +660 -0
- package/engineering/skills/physics-in-loop/SKILL.md +238 -0
- package/engineering/skills/physics-in-loop/references/feadocs.md +142 -0
- package/engineering/skills/solidworks-bridge/SKILL.md +445 -0
- package/engineering/skills/sw-design/SKILL.md +112 -0
- package/engineering/skills/sw-design/references/autocad-workflow.md +81 -0
- package/engineering/skills/sw-design/references/gbt-drafting.md +71 -0
- package/engineering/skills/sw-to-cad/SKILL.md +121 -0
- package/engineering/skills/webots-simulation/SKILL.md +58 -0
- package/engineering/tools/LICENSE.Auto-SolidWorks +21 -0
- package/engineering/tools/_plugin_paths.py +93 -0
- package/engineering/tools/_root.py +330 -0
- package/engineering/tools/_store.py +446 -0
- package/engineering/tools/ac_bridge.py +459 -0
- package/engineering/tools/ac_rules.json +21 -0
- package/engineering/tools/ac_validate.py +699 -0
- package/engineering/tools/ask_user.py +887 -0
- package/engineering/tools/build_arm1.py +46 -0
- package/engineering/tools/build_assembly.py +94 -0
- package/engineering/tools/build_base.py +57 -0
- package/engineering/tools/build_housing.py +70 -0
- package/engineering/tools/build_structural.py +43 -0
- package/engineering/tools/build_transmission.py +69 -0
- package/engineering/tools/cadx.lsp +128 -0
- package/engineering/tools/choice_contract.py +61 -0
- package/engineering/tools/conn_state.py +1671 -0
- package/engineering/tools/defense_gate.py +3374 -0
- package/engineering/tools/drawing_input.py +314 -0
- package/engineering/tools/env_check.py +467 -0
- package/engineering/tools/examples/build_axis.py +142 -0
- package/engineering/tools/examples/build_cup.py +56 -0
- package/engineering/tools/examples/build_plate.py +58 -0
- package/engineering/tools/examples/build_sphere.py +30 -0
- package/engineering/tools/examples/build_transition_duct.py +381 -0
- package/engineering/tools/gate_integrity.py +401 -0
- package/engineering/tools/mode2_flow.py +3589 -0
- package/engineering/tools/mode_checker.py +77 -0
- package/engineering/tools/mode_gate.py +8013 -0
- package/engineering/tools/part_registry.py +4266 -0
- package/engineering/tools/physics/__init__.py +50 -0
- package/engineering/tools/physics/_compat.py +12 -0
- package/engineering/tools/physics/design_state.py +142 -0
- package/engineering/tools/physics/domain_validator.py +751 -0
- package/engineering/tools/physics/fatigue.py +489 -0
- package/engineering/tools/physics/fea_solver.py +1163 -0
- package/engineering/tools/physics/feapy_solver.py +314 -0
- package/engineering/tools/physics/gb_materials.json +1228 -0
- package/engineering/tools/physics/geometry_gate.py +337 -0
- package/engineering/tools/physics/load_case.py +951 -0
- package/engineering/tools/physics/material_db.py +874 -0
- package/engineering/tools/physics/mesh_adapter.py +321 -0
- package/engineering/tools/physics/refine_rules.py +272 -0
- package/engineering/tools/physics/rules/gb_base_rules.json +377 -0
- package/engineering/tools/physics/rules/gb_drafting_rules.json +57 -0
- package/engineering/tools/physics/rules/gb_electrical_rules.json +25 -0
- package/engineering/tools/physics/rules/gb_fasteners_rules.json +73 -0
- package/engineering/tools/physics/rules/gb_housing_rules.json +41 -0
- package/engineering/tools/physics/rules/gb_manufacturing_rules.json +25 -0
- package/engineering/tools/physics/rules/gb_material_verify_rules.json +72 -0
- package/engineering/tools/physics/rules/gb_materials_rules.json +377 -0
- package/engineering/tools/physics/rules/gb_safety_rules.json +89 -0
- package/engineering/tools/physics/rules/gb_springs_rules.json +41 -0
- package/engineering/tools/physics/rules/gb_standards_registry.json +1646 -0
- package/engineering/tools/physics/rules/gb_structural_rules.json +201 -0
- package/engineering/tools/physics/rules/gb_surface_rules.json +25 -0
- package/engineering/tools/physics/rules/gb_transmission_rules.json +233 -0
- package/engineering/tools/physics/rules/housing_rules.json +48 -0
- package/engineering/tools/physics/rules/manifest.json +54 -0
- package/engineering/tools/physics/rules/mold_rules.json +48 -0
- package/engineering/tools/physics/rules/structural_rules.json +56 -0
- package/engineering/tools/physics/rules/transmission_rules.json +48 -0
- package/engineering/tools/physics/simulation_report.py +829 -0
- package/engineering/tools/physics_bridge.py +2685 -0
- package/engineering/tools/profile-manifest.ps1 +72 -0
- package/engineering/tools/run_regression.py +109 -0
- package/engineering/tools/settings.py +419 -0
- package/engineering/tools/solidworks-modeling.md +519 -0
- package/engineering/tools/sw_bridge.py +8215 -0
- package/engineering/tools/sw_guard.py +1066 -0
- package/engineering/tools/sw_surface.py +87 -0
- package/engineering/tools/swapi.py +14766 -0
- package/engineering/tools/verify_sync.py +218 -0
- package/engineering/tools/visual_check.py +2589 -0
- package/engineering/tools/webots_env.py +121 -0
- package/engineering/tools/webots_mcp/__init__.py +3 -0
- package/engineering/tools/webots_mcp/__main__.py +3 -0
- package/engineering/tools/webots_mcp/assembly.py +391 -0
- package/engineering/tools/webots_mcp/bootstrap.py +55 -0
- package/engineering/tools/webots_mcp/controller_worker.py +124 -0
- package/engineering/tools/webots_mcp/evidence.py +288 -0
- package/engineering/tools/webots_mcp/fake_worker.py +69 -0
- package/engineering/tools/webots_mcp/fault_resources.py +121 -0
- package/engineering/tools/webots_mcp/host.py +45 -0
- package/engineering/tools/webots_mcp/ledger.py +73 -0
- package/engineering/tools/webots_mcp/owned_job.py +70 -0
- package/engineering/tools/webots_mcp/physical_gate.py +187 -0
- package/engineering/tools/webots_mcp/physics.py +152 -0
- package/engineering/tools/webots_mcp/pipeline.py +108 -0
- package/engineering/tools/webots_mcp/recipes.py +129 -0
- package/engineering/tools/webots_mcp/repair.py +132 -0
- package/engineering/tools/webots_mcp/repair_trial.py +375 -0
- package/engineering/tools/webots_mcp/runtime.py +311 -0
- package/engineering/tools/webots_mcp/server.py +217 -0
- package/engineering/tools/webots_mcp/sw_export.py +509 -0
- package/engineering/tools/webots_mcp/transport.py +156 -0
- package/engineering/tools/workflow_gate.py +6134 -0
- package/package.json +66 -6
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""feapy_solver.py -- Pure Python FEA (2D CST + 3D CTE)"""
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MaterialPropertyError(ValueError):
|
|
7
|
+
"""材料属性缺失/非法(fail-closed:宁可不计算,也不给出错误结论)。"""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _require_positive(value, name):
|
|
11
|
+
"""校验材料参数为【正的有限数】,否则抛 MaterialPropertyError。
|
|
12
|
+
|
|
13
|
+
── 【PH-01 修复】为什么必须在这里兜底 ────────────────────────────────
|
|
14
|
+
实测缺陷:swapi.COMMON_MATERIALS 原先【全部材料都缺 poissons_ratio】,
|
|
15
|
+
于是 nu=None 一路传到 `nu**2` → TypeError:
|
|
16
|
+
unsupported operand type(s) for ** or pow(): 'NoneType' and 'int'
|
|
17
|
+
整个 FEA 挂掉,SF=N/A、mass=null、status=UNKNOWN,物理防线整体失效。
|
|
18
|
+
根治是补全材料表(已做),但【不该只靠数据齐全】—— 只要上游任何环节
|
|
19
|
+
漏传/传 None,就会以同样的方式崩在深处。因此在入口做 fail-closed:
|
|
20
|
+
参数非法 → 抛明确异常,由调用方转成"不可判定",绝不用 nan/0 顶替。
|
|
21
|
+
"""
|
|
22
|
+
if value is None:
|
|
23
|
+
raise MaterialPropertyError(
|
|
24
|
+
"材料参数 %s 缺失(None)—— 无法计算;"
|
|
25
|
+
"请检查材料是否真正赋到零件上(密度=1000/属性缺省都属未赋材)" % name)
|
|
26
|
+
if isinstance(value, bool):
|
|
27
|
+
raise MaterialPropertyError("材料参数 %s 是布尔值,不是数值" % name)
|
|
28
|
+
try:
|
|
29
|
+
v = float(value)
|
|
30
|
+
except Exception:
|
|
31
|
+
raise MaterialPropertyError("材料参数 %s=%r 不是数值" % (name, value))
|
|
32
|
+
if v != v or v in (float("inf"), float("-inf")):
|
|
33
|
+
raise MaterialPropertyError("材料参数 %s=%r 不是有限数" % (name, value))
|
|
34
|
+
if v <= 0:
|
|
35
|
+
raise MaterialPropertyError("材料参数 %s=%r 必须为正数" % (name, value))
|
|
36
|
+
return v
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _check_nu(nu):
|
|
40
|
+
"""泊松比专项校验:必须落在 (-1, 0.5) —— 这是各向同性弹性体的物理边界。
|
|
41
|
+
|
|
42
|
+
0.5 对应不可压缩材料(会导致体积模量无穷大、刚度矩阵奇异);
|
|
43
|
+
负值超出热力学稳定范围。这里明确拦下,避免"算出结果但结果无意义"。
|
|
44
|
+
"""
|
|
45
|
+
# ⚠️ 先做类型/缺失校验:直接写 float(nu) 会在 nu=None 时抛 TypeError,
|
|
46
|
+
# 那是"崩在深处",而不是 fail-closed 的明确拒绝(PH-01 的原始症状)。
|
|
47
|
+
if nu is None:
|
|
48
|
+
raise MaterialPropertyError(
|
|
49
|
+
"材料参数 nu(泊松比)缺失(None)—— 无法组装刚度矩阵;"
|
|
50
|
+
"请检查材料是否真正赋到零件上(未赋材/属性缺省都会导致 None)")
|
|
51
|
+
if isinstance(nu, bool):
|
|
52
|
+
raise MaterialPropertyError("材料参数 nu(泊松比)是布尔值,不是数值")
|
|
53
|
+
try:
|
|
54
|
+
_nv = float(nu)
|
|
55
|
+
except Exception:
|
|
56
|
+
raise MaterialPropertyError("材料参数 nu=%r 不是数值" % (nu,))
|
|
57
|
+
if _nv != _nv or _nv in (float("inf"), float("-inf")):
|
|
58
|
+
raise MaterialPropertyError("材料参数 nu=%r 不是有限数" % (nu,))
|
|
59
|
+
_require_positive(1.0 - 2.0 * _nv, "nu(1-2ν)") # 排除 nu >= 0.5
|
|
60
|
+
_require_positive(1.0 + _nv, "nu(1+ν)") # 排除 nu <= -1
|
|
61
|
+
return _nv
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _D_2d(E, nu, model):
|
|
65
|
+
E = _require_positive(E, "E")
|
|
66
|
+
nu = _check_nu(nu)
|
|
67
|
+
if model == "plane_stress":
|
|
68
|
+
return E / (1 - nu**2) * np.array([[1, nu, 0], [nu, 1, 0], [0, 0, (1 - nu) / 2]])
|
|
69
|
+
elif model == "plane_strain":
|
|
70
|
+
return E / ((1 + nu) * (1 - 2 * nu)) * np.array([[1 - nu, nu, 0], [nu, 1 - nu, 0], [0, 0, (1 - 2 * nu) / 2]])
|
|
71
|
+
raise ValueError(f"Unknown 2D model: {model}")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _D_3d(E, nu):
|
|
75
|
+
E = _require_positive(E, "E")
|
|
76
|
+
nu = _check_nu(nu)
|
|
77
|
+
la = E * nu / ((1 + nu) * (1 - 2 * nu))
|
|
78
|
+
mu = E / (2 * (1 + nu))
|
|
79
|
+
return np.array([[la + 2*mu, la, la, 0, 0, 0],
|
|
80
|
+
[la, la+2*mu, la, 0, 0, 0],
|
|
81
|
+
[la, la, la+2*mu, 0, 0, 0],
|
|
82
|
+
[0, 0, 0, mu, 0, 0],
|
|
83
|
+
[0, 0, 0, 0, mu, 0],
|
|
84
|
+
[0, 0, 0, 0, 0, mu]])
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _cst_B(x1,y1,x2,y2,x3,y3,area):
|
|
88
|
+
if area < 1e-10: return np.zeros((3,6))
|
|
89
|
+
b1,b2,b3=y2-y3,y3-y1,y1-y2
|
|
90
|
+
c1,c2,c3=x3-x2,x1-x3,x2-x1
|
|
91
|
+
return np.array([[b1,0,b2,0,b3,0],[0,c1,0,c2,0,c3],[c1,b1,c2,b2,c3,b3]])/(2.0*area)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _cte_B_vol(coords):
|
|
95
|
+
x1,y1,z1=coords[0]; x2,y2,z2=coords[1]
|
|
96
|
+
x3,y3,z3=coords[2]; x4,y4,z4=coords[3]
|
|
97
|
+
vol=(x2-x1)*((y3-y1)*(z4-z1)-(z3-z1)*(y4-y1)) \
|
|
98
|
+
-(y2-y1)*((x3-x1)*(z4-z1)-(z3-z1)*(x4-x1)) \
|
|
99
|
+
+(z2-z1)*((x3-x1)*(y4-y1)-(y3-y1)*(x4-x1))
|
|
100
|
+
vol=abs(vol)/6.0
|
|
101
|
+
if vol<1e-10: return np.zeros((6,12)),0.0
|
|
102
|
+
M=np.array([[1,x1,y1,z1],[1,x2,y2,z2],[1,x3,y3,z3],[1,x4,y4,z4]])
|
|
103
|
+
invM=np.linalg.inv(M)
|
|
104
|
+
bz=[invM[1,0],invM[1,1],invM[1,2],invM[1,3]]
|
|
105
|
+
cz=[invM[2,0],invM[2,1],invM[2,2],invM[2,3]]
|
|
106
|
+
dz=[invM[3,0],invM[3,1],invM[3,2],invM[3,3]]
|
|
107
|
+
B=np.zeros((6,12))
|
|
108
|
+
for i in range(4):
|
|
109
|
+
B[0,i*3]=bz[i]; B[1,i*3+1]=cz[i]; B[2,i*3+2]=dz[i]
|
|
110
|
+
B[3,i*3+1]=dz[i]; B[3,i*3+2]=cz[i]
|
|
111
|
+
B[4,i*3]=dz[i]; B[4,i*3+2]=bz[i]
|
|
112
|
+
B[5,i*3]=cz[i]; B[5,i*3+1]=bz[i]
|
|
113
|
+
return B,vol
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def mesh_rect(x0,x1,y0,y1,nx,ny):
|
|
117
|
+
nodes=[]
|
|
118
|
+
for i in range(nx+1):
|
|
119
|
+
for j in range(ny+1):
|
|
120
|
+
nodes.append([x0+i*(x1-x0)/nx, y0+j*(y1-y0)/ny, 0.0])
|
|
121
|
+
nodes=np.array(nodes)
|
|
122
|
+
elems=[]
|
|
123
|
+
for i in range(nx):
|
|
124
|
+
for j in range(ny):
|
|
125
|
+
idx=i*(ny+1)+j
|
|
126
|
+
elems.append([idx,idx+1,idx+(ny+1)])
|
|
127
|
+
elems.append([idx+1,idx+(ny+1)+1,idx+(ny+1)])
|
|
128
|
+
return nodes,np.array(elems)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def mesh_box(x0,x1,y0,y1,z0,z1,nx,ny,nz):
|
|
132
|
+
nodes=[]
|
|
133
|
+
for i in range(nx+1):
|
|
134
|
+
for j in range(ny+1):
|
|
135
|
+
for k in range(nz+1):
|
|
136
|
+
nodes.append([x0+i*(x1-x0)/nx, y0+j*(y1-y0)/ny, z0+k*(z1-z0)/nz])
|
|
137
|
+
nodes=np.array(nodes)
|
|
138
|
+
elems=[]
|
|
139
|
+
for i in range(nx):
|
|
140
|
+
for j in range(ny):
|
|
141
|
+
for k in range(nz):
|
|
142
|
+
idx=i*(ny+1)*(nz+1)+j*(nz+1)+k
|
|
143
|
+
v=[idx,idx+1,idx+(nz+1),idx+(nz+1)+1,
|
|
144
|
+
idx+(ny+1)*(nz+1),idx+(ny+1)*(nz+1)+1,
|
|
145
|
+
idx+(ny+1)*(nz+1)+(nz+1),idx+(ny+1)*(nz+1)+(nz+1)+1]
|
|
146
|
+
elems.append([v[0],v[1],v[2],v[4]])
|
|
147
|
+
elems.append([v[1],v[3],v[2],v[6]])
|
|
148
|
+
elems.append([v[1],v[5],v[3],v[7]])
|
|
149
|
+
elems.append([v[1],v[2],v[3],v[6]])
|
|
150
|
+
elems.append([v[1],v[3],v[6],v[7]])
|
|
151
|
+
elems.append([v[1],v[4],v[5],v[7]])
|
|
152
|
+
return nodes,np.array(elems)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class FEASolver:
|
|
156
|
+
def __init__(self,nodes,elements,model="plane_stress",thickness=1.0):
|
|
157
|
+
self.nodes=np.array(nodes); self.elements=np.array(elements)
|
|
158
|
+
self.model=model; self.N=len(nodes); self.thickness=thickness
|
|
159
|
+
self.nd=3 if model=="3d" else 2
|
|
160
|
+
self.ndof=self.N*self.nd
|
|
161
|
+
|
|
162
|
+
def solve(self,E,nu,fixed=None,loads=None):
|
|
163
|
+
D=_D_3d(E,nu) if self.model=="3d" else _D_2d(E,nu,self.model)
|
|
164
|
+
nd=self.nd; K=np.zeros((self.ndof,self.ndof))
|
|
165
|
+
Bcache={}
|
|
166
|
+
elem_volumes=[]
|
|
167
|
+
|
|
168
|
+
for elem in self.elements:
|
|
169
|
+
nn=[int(elem[m]) for m in range(len(elem))]
|
|
170
|
+
key=tuple(nn); coords=self.nodes[nn]
|
|
171
|
+
if nd==2:
|
|
172
|
+
x1,y1,_=coords[0]; x2,y2,_=coords[1]; x3,y3,_=coords[2]
|
|
173
|
+
area=abs(x1*(y2-y3)+x2*(y3-y1)+x3*(y1-y2))*0.5
|
|
174
|
+
Bcache[key]=_cst_B(x1,y1,x2,y2,x3,y3,area)
|
|
175
|
+
elem_volumes.append(("2d",area))
|
|
176
|
+
else:
|
|
177
|
+
Bcache[key],vol=_cte_B_vol(coords)
|
|
178
|
+
elem_volumes.append(("3d",vol))
|
|
179
|
+
|
|
180
|
+
B=Bcache[key]; val=elem_volumes[-1][1]
|
|
181
|
+
if nd==2:
|
|
182
|
+
Ke=B.T@D@B*val*self.thickness
|
|
183
|
+
else:
|
|
184
|
+
Ke=B.T@D@B*val
|
|
185
|
+
dofs=[]
|
|
186
|
+
for n in nn:
|
|
187
|
+
for d in range(nd):
|
|
188
|
+
dofs.append(n*nd+d)
|
|
189
|
+
for a in range(len(dofs)):
|
|
190
|
+
for bb in range(len(dofs)):
|
|
191
|
+
K[dofs[a],dofs[bb]]+=Ke[a,bb]
|
|
192
|
+
|
|
193
|
+
F=np.zeros(self.ndof)
|
|
194
|
+
if loads:
|
|
195
|
+
for item in loads:
|
|
196
|
+
ni=int(item[0])
|
|
197
|
+
F[ni*nd]+=item[1] if len(item)>1 else 0
|
|
198
|
+
F[ni*nd+1]+=item[2] if len(item)>2 else 0
|
|
199
|
+
if nd==3 and len(item)>3:
|
|
200
|
+
F[ni*nd+2]+=item[3]
|
|
201
|
+
if fixed:
|
|
202
|
+
pen=1e15
|
|
203
|
+
for item in fixed:
|
|
204
|
+
ni,d=int(item[0]),int(item[1])
|
|
205
|
+
K[ni*nd+d,ni*nd+d]+=pen
|
|
206
|
+
U=np.linalg.solve(K,F)
|
|
207
|
+
|
|
208
|
+
# von Mises
|
|
209
|
+
vm=[]
|
|
210
|
+
for idx,elem in enumerate(self.elements):
|
|
211
|
+
nn=[int(elem[m]) for m in range(len(elem))]
|
|
212
|
+
key=tuple(nn); B=Bcache[key]
|
|
213
|
+
etype,vval=elem_volumes[idx]
|
|
214
|
+
if nd==2:
|
|
215
|
+
u_e=[]
|
|
216
|
+
for m in range(3):
|
|
217
|
+
n=nn[m]
|
|
218
|
+
u_e.append(U[n*2]); u_e.append(U[n*2+1])
|
|
219
|
+
strain=B@np.array(u_e); stress=D@strain
|
|
220
|
+
sx,sy,txy=stress
|
|
221
|
+
vm.append(np.sqrt(sx**2-sx*sy+sy**2+3*txy**2))
|
|
222
|
+
else:
|
|
223
|
+
u_e=[]
|
|
224
|
+
for m in range(4):
|
|
225
|
+
n=nn[m]
|
|
226
|
+
u_e.append(U[n*3]); u_e.append(U[n*3+1]); u_e.append(U[n*3+2])
|
|
227
|
+
strain=B@np.array(u_e); stress=D@strain
|
|
228
|
+
sx,sy,sz,tyz,txz,txy=stress
|
|
229
|
+
vm.append(np.sqrt(0.5*((sx-sy)**2+(sy-sz)**2+(sz-sx)**2+6*(tyz**2+txz**2+txy**2))))
|
|
230
|
+
|
|
231
|
+
if nd==2:
|
|
232
|
+
disp=np.sqrt(U[0::2]**2+U[1::2]**2)
|
|
233
|
+
else:
|
|
234
|
+
disp=np.sqrt(U[0::3]**2+U[1::3]**2+U[2::3]**2)
|
|
235
|
+
|
|
236
|
+
return {"max_displacement_mm":float(disp.max()),"max_von_mises_mpa":float(max(vm)) if vm else 0.0,"displacement_field":U,"von_mises_per_element":vm}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def solve_cantilever_2d(L,H,T,F_load,E,nu,nx=40,ny=12):
|
|
240
|
+
nodes,elems=mesh_rect(0,L,0,H,nx,ny)
|
|
241
|
+
s=FEASolver(nodes,elems,model="plane_stress",thickness=T)
|
|
242
|
+
fixed=[]
|
|
243
|
+
for j in range(ny+1):
|
|
244
|
+
n=j; fixed.append((n,0)); fixed.append((n,1))
|
|
245
|
+
loads=[]
|
|
246
|
+
nr=nx*(ny+1)
|
|
247
|
+
for j in range(ny+1):
|
|
248
|
+
n=nr+j; loads.append((n,0.0,-F_load/(ny+1)))
|
|
249
|
+
r=s.solve(E,nu,fixed=fixed,loads=loads)
|
|
250
|
+
I=T*H**3/12; de=F_load*L**3/(3*E*I); se=6*F_load*L/(T*H**2)
|
|
251
|
+
r["analytical"]={"delta_mm":de,"sigma_mpa":se,"disp_error_pct":abs(r["max_displacement_mm"]/de-1)*100 if de else 0,"stress_error_pct":abs(r["max_von_mises_mpa"]/se-1)*100 if se else 0}
|
|
252
|
+
return r
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def solve_cantilever_3d(L,H,D,F_load,E,nu,nx=10,ny=5,nz=5):
|
|
256
|
+
nodes,elems=mesh_box(0,L,0,H,0,D,nx,ny,nz)
|
|
257
|
+
s=FEASolver(nodes,elems,model="3d")
|
|
258
|
+
tol=L/(2*nx)
|
|
259
|
+
fixed=[]
|
|
260
|
+
for ii,node in enumerate(nodes):
|
|
261
|
+
if node[0]<tol:
|
|
262
|
+
fixed.append((ii,0)); fixed.append((ii,1)); fixed.append((ii,2))
|
|
263
|
+
rn=[]
|
|
264
|
+
for ii,node in enumerate(nodes):
|
|
265
|
+
if node[0]>L-tol: rn.append(ii)
|
|
266
|
+
rc=len(rn)
|
|
267
|
+
loads=[]
|
|
268
|
+
for ii in rn:
|
|
269
|
+
loads.append((ii,0.0,-F_load/rc))
|
|
270
|
+
r=s.solve(E,nu,fixed=fixed,loads=loads)
|
|
271
|
+
I=D*H**3/12; de=F_load*L**3/(3*E*I); se=6*F_load*L/(D*H**2)
|
|
272
|
+
r["analytical"]={"delta_mm":de,"sigma_mpa":se,"disp_error_pct":abs(r["max_displacement_mm"]/de-1)*100 if de else 0,"stress_error_pct":abs(r["max_von_mises_mpa"]/se-1)*100 if se else 0}
|
|
273
|
+
return r
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def solve_cantilever(L, H, W_or_T, F_load, E, nu, nx=10, ny=5, nz=0):
|
|
277
|
+
"""兼容接口:自动检测 2D/3D。
|
|
278
|
+
|
|
279
|
+
- 若 nz==0 或 W_or_T 与 H 同量级 → 2D 平面应力
|
|
280
|
+
- 否则 → 3D 四面体
|
|
281
|
+
"""
|
|
282
|
+
if nz == 0 or abs(W_or_T - H) < 1e-9:
|
|
283
|
+
return solve_cantilever_2d(L, H, W_or_T, F_load, E, nu, nx=nx, ny=ny)
|
|
284
|
+
else:
|
|
285
|
+
return solve_cantilever_3d(L, H, W_or_T, F_load, E, nu, nx=nx, ny=ny, nz=nz)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
if __name__=="__main__":
|
|
289
|
+
import sys; sys.stdout.reconfigure(encoding="utf-8")
|
|
290
|
+
print("="*60); print("2D+3D FEA Test"); print("="*60); print()
|
|
291
|
+
print("[1] 2D Cantilever")
|
|
292
|
+
r2d=solve_cantilever_2d(200,60,60,500,68900,0.33,nx=40,ny=12)
|
|
293
|
+
n2d,e2d=mesh_rect(0,200,0,60,40,12)
|
|
294
|
+
print(f" Mesh: {len(n2d)} nodes, {len(e2d)} elems")
|
|
295
|
+
print(f" Disp: {r2d['max_displacement_mm']:.4f}mm (exact:{r2d['analytical']['delta_mm']:.4f}, err:{r2d['analytical']['disp_error_pct']:.1f}%)")
|
|
296
|
+
print(f" Sigma: {r2d['max_von_mises_mpa']:.2f}MPa (exact:{r2d['analytical']['sigma_mpa']:.2f}, err:{r2d['analytical']['stress_error_pct']:.1f}%)")
|
|
297
|
+
print(f" SF: {276.0/r2d['max_von_mises_mpa']:.2f}")
|
|
298
|
+
print()
|
|
299
|
+
print("[2] 3D Cantilever (10x5x5)")
|
|
300
|
+
r3d=solve_cantilever_3d(200,60,60,500,68900,0.33,nx=10,ny=5,nz=5)
|
|
301
|
+
n3d,e3d=mesh_box(0,200,0,60,0,60,10,5,5)
|
|
302
|
+
print(f" Mesh: {len(n3d)} nodes, {len(e3d)} tets")
|
|
303
|
+
print(f" Disp: {r3d['max_displacement_mm']:.4f}mm (exact:{r3d['analytical']['delta_mm']:.4f}, err:{r3d['analytical']['disp_error_pct']:.1f}%)")
|
|
304
|
+
print(f" Sigma: {r3d['max_von_mises_mpa']:.2f}MPa (exact:{r3d['analytical']['sigma_mpa']:.2f}, err:{r3d['analytical']['stress_error_pct']:.1f}%)")
|
|
305
|
+
print(f" SF: {276.0/r3d['max_von_mises_mpa']:.2f}")
|
|
306
|
+
print()
|
|
307
|
+
print("[3] 3D Convergence")
|
|
308
|
+
for nv in [(3,2,2),(5,3,3),(8,4,4),(10,5,5)]:
|
|
309
|
+
r=solve_cantilever_3d(200,60,60,500,68900,0.33,nx=nv[0],ny=nv[1],nz=nv[2])
|
|
310
|
+
nc,ec=mesh_box(0,200,0,60,0,60,nv[0],nv[1],nv[2])
|
|
311
|
+
sf=276.0/r["max_von_mises_mpa"]
|
|
312
|
+
print(f" {nv[0]}x{nv[1]}x{nv[2]}: {len(nc)}n/{len(ec)}t | disp={r['max_displacement_mm']:.4f}mm ({r['analytical']['disp_error_pct']:+.1f}%) | sigma={r['max_von_mises_mpa']:.2f}MPa ({r['analytical']['stress_error_pct']:+.1f}%) | SF={sf:.2f}")
|
|
313
|
+
print()
|
|
314
|
+
print("="*60); print("All tests passed!"); print("="*60)
|