Skip to content

Commit ab3aa29

Browse files
thisistheplaceBen Cannell
and
Ben Cannell
authored
Feature/radial mesh controls (#21)
* Started on adding radial mesh controls * Ran black * Progress on mesh constraints * Changed element type to quad * Added element type enum * Quad updates * Updated github action ubuntu version: Co-authored-by: Ben Cannell <[email protected]>
1 parent 4986861 commit ab3aa29

File tree

24 files changed

+304
-156
lines changed

24 files changed

+304
-156
lines changed

.github/workflows/test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on: [push]
44

55
jobs:
66
test:
7-
runs-on: ubuntu-latest
7+
runs-on: ubuntu-22.04
88
if: github.ref == 'refs/heads/main'
99

1010
strategy:

.vscode/settings.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"python.testing.pytestArgs": [
3-
"."
3+
"src"
44
],
55
"python.testing.unittestEnabled": false,
66
"python.testing.pytestEnabled": true

Dockerfile-debug

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ RUN pip install pytest
1515

1616
# Install Python dependencies
1717
ADD requirements.txt .
18-
RUN pip install --no-cache-dir -r requirements.txt && pip install --no-cache-dir gunicorn
18+
RUN pip install --no-cache-dir -r requirements.txt
1919

2020
WORKDIR /workspaces/joint_model

docker-compose.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ services:
44
fastapi:
55
container_name: jointrest
66
environment:
7-
- RESTAPI_URL=http://127.0.0.1:8000
7+
- RESTAPI_URL=http://127.0.0.1:8020
88
- VIEWER_URL=http://127.0.0.1:8050
99
restart: always
1010
build:
@@ -13,15 +13,15 @@ services:
1313
expose:
1414
- "8000"
1515
ports:
16-
- "8000:8000"
16+
- "8020:8000"
1717
command: uvicorn app.app_rest:app --host 0.0.0.0 --port 8000 --proxy-headers --workers 1
1818

1919
viewer:
2020
depends_on:
2121
- fastapi
2222
container_name: jointviewer
2323
environment:
24-
- RESTAPI_URL=http://host.docker.internal:8000
24+
- RESTAPI_URL=http://host.docker.internal:8020
2525
- VIEWER_URL=http://host.docker.internal:8050
2626
restart: always
2727
build:

src/app/converters/mesh.py

+19-17
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,35 @@
11
from collections import defaultdict
2+
from enum import Enum
23
import gmsh
34

45
from ..interfaces import DashVtkMesh
56

7+
class ElementType(str, Enum):
8+
TRIANGLE = "Triangle"
9+
QUADRANGLE = "Quadrangle"
610

7-
def process_mesh(mesh: gmsh.model.mesh):
8-
# Like elements, mesh edges and faces are described by (an ordered list of)
9-
# their nodes. Let us retrieve the edges and the (triangular) faces of all the
10-
# first order tetrahedra in the mesh:
11-
elementType = mesh.getElementType("Triangle", 1)
12-
# edgeNodes = mesh.getElementEdgeNodes(elementType)
13-
faceNodes = mesh.getElementFaceNodes(elementType, 3)
11+
NUM_NODES = {
12+
ElementType.TRIANGLE: 3,
13+
ElementType.QUADRANGLE: 4
14+
}
1415

15-
# Edges and faces are returned for each element as a list of nodes corresponding
16-
# to the canonical orientation of the edges and faces for a given element type.
17-
# Edge and face tags can then be retrieved by providing their nodes:
18-
# edgeTags, edgeOrientations = mesh.getEdges(edgeNodes)
19-
faceTags, _ = mesh.getFaces(3, faceNodes)
16+
def process_mesh(mesh: gmsh.model.mesh, eltype: ElementType):
17+
elementType = mesh.getElementType(eltype.value, 1)
18+
faceNodes = mesh.getElementFaceNodes(elementType, NUM_NODES[eltype])
19+
20+
faceTags, _ = mesh.getFaces(NUM_NODES[eltype], faceNodes)
2021
elementTags, _ = mesh.getElementsByType(elementType)
2122

2223
faces2Elements = defaultdict(list)
2324

24-
for i in range(len(faceTags)): # 4 faces per tetrahedron
25-
faces2Elements[faceTags[i]].append(elementTags[i // 4])
25+
for i in range(len(faceTags)):
26+
faces2Elements[faceTags[i]].append(elementTags[i])
2627

2728
return faces2Elements
2829

2930

30-
def mesh_to_dash_vtk(mesh: gmsh.model.mesh) -> DashVtkMesh:
31-
face2el = process_mesh(mesh)
31+
def mesh_to_dash_vtk(mesh: gmsh.model.mesh, eltype: ElementType) -> DashVtkMesh:
32+
face2el = process_mesh(mesh, eltype)
3233

3334
outerfaces = [k for k in face2el.keys()]
3435

@@ -41,7 +42,7 @@ def mesh_to_dash_vtk(mesh: gmsh.model.mesh) -> DashVtkMesh:
4142
_, node_ids = mesh.getElement(element)
4243
# if eltype != 2 or len(node_ids) != 3:
4344
# continue
44-
line = [len(node_ids)]
45+
line = [len(node_ids) + 1]
4546
poly = [len(node_ids)]
4647
for nid in node_ids:
4748
if nid not in nid2pointidx:
@@ -50,6 +51,7 @@ def mesh_to_dash_vtk(mesh: gmsh.model.mesh) -> DashVtkMesh:
5051
nid2pointidx[nid] = int(len(points) / 3 - 1)
5152
line.append(nid2pointidx[nid])
5253
poly.append(nid2pointidx[nid])
54+
line.append(line[1])
5355

5456
lines += line
5557
polys += poly

src/app/converters/model.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
from .mesh import mesh_to_dash_vtk
1+
from .mesh import mesh_to_dash_vtk, ElementType
22

33
from ..interfaces import *
44
from ..modelling.mesher.mesh import mesh_model
55

66

77
def convert_model_to_dash_vtk(model: Model) -> DashVtkModel:
88
with mesh_model(model, MeshSpecs(size=0.1)) as mesh:
9-
mesh = mesh_to_dash_vtk(mesh)
9+
mesh = mesh_to_dash_vtk(mesh, ElementType.QUADRANGLE)
1010
return DashVtkModel(name=model.name, mesh=mesh)

src/app/interfaces/examples/joints.py

+30-25
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,18 @@
77
joint=Joint(
88
name="TJoint",
99
master=Tubular(
10-
name="Vertical",
11-
axis=Axis3D(
12-
point=Point3D(x=0, y=0, z=-5), vector=Vector3D(x=0, y=0, z=10)
13-
),
14-
diameter=0.5,
10+
name="Vertical",
11+
axis=Axis3D(
12+
point=Point3D(x=0, y=0, z=-5), vector=Vector3D(x=0, y=0, z=10)
1513
),
14+
diameter=0.5,
15+
),
1616
slaves=[
1717
Tubular(
1818
name="Horizontal",
1919
axis=Axis3D(
20-
point=Point3D(x=0., y=0.25, z=0), vector=Vector3D(x=0, y=3, z=0)
20+
point=Point3D(x=0.0, y=0.25, z=0),
21+
vector=Vector3D(x=0, y=3, z=0),
2122
),
2223
diameter=0.25,
2324
),
@@ -29,17 +30,18 @@
2930
joint=Joint(
3031
name="TAngle",
3132
master=Tubular(
32-
name="Vertical",
33-
axis=Axis3D(
34-
point=Point3D(x=0, y=0, z=-5), vector=Vector3D(x=0, y=0, z=10)
35-
),
36-
diameter=0.5,
33+
name="Vertical",
34+
axis=Axis3D(
35+
point=Point3D(x=0, y=0, z=-5), vector=Vector3D(x=0, y=0, z=10)
3736
),
37+
diameter=0.5,
38+
),
3839
slaves=[
3940
Tubular(
4041
name="Horizontal",
4142
axis=Axis3D(
42-
point=Point3D(x=0., y=0.25, z=0), vector=Vector3D(x=0, y=3, z=3)
43+
point=Point3D(x=0.0, y=0.25, z=0),
44+
vector=Vector3D(x=0, y=3, z=3),
4345
),
4446
diameter=0.25,
4547
),
@@ -51,12 +53,12 @@
5153
joint=Joint(
5254
name="TOffset",
5355
master=Tubular(
54-
name="Vertical",
55-
axis=Axis3D(
56-
point=Point3D(x=0, y=0, z=-5), vector=Vector3D(x=0, y=0, z=10)
57-
),
58-
diameter=0.5,
56+
name="Vertical",
57+
axis=Axis3D(
58+
point=Point3D(x=0, y=0, z=-5), vector=Vector3D(x=0, y=0, z=10)
5959
),
60+
diameter=0.5,
61+
),
6062
slaves=[
6163
Tubular(
6264
name="Horizontal",
@@ -73,12 +75,12 @@
7375
joint=Joint(
7476
name="KJoint",
7577
master=Tubular(
76-
name="Chord",
77-
axis=Axis3D(
78-
point=Point3D(x=0, y=0, z=-5), vector=Vector3D(x=0, y=0, z=10)
79-
),
80-
diameter=0.6,
78+
name="Chord",
79+
axis=Axis3D(
80+
point=Point3D(x=0, y=0, z=-5), vector=Vector3D(x=0, y=0, z=10)
8181
),
82+
diameter=0.6,
83+
),
8284
slaves=[
8385
Tubular(
8486
name="Brace1",
@@ -90,21 +92,24 @@
9092
Tubular(
9193
name="Brace2",
9294
axis=Axis3D(
93-
point=Point3D(x=-0.3, y=0.3, z=1), vector=Vector3D(x=-2, y=2, z=2)
95+
point=Point3D(x=-0.3, y=0.3, z=1),
96+
vector=Vector3D(x=-2, y=2, z=2),
9497
),
9598
diameter=0.25,
9699
),
97100
Tubular(
98101
name="Brace3",
99102
axis=Axis3D(
100-
point=Point3D(x=0.3, y=0.3, z=-1), vector=Vector3D(x=2, y=2, z=-2)
103+
point=Point3D(x=0.3, y=0.3, z=-1),
104+
vector=Vector3D(x=2, y=2, z=-2),
101105
),
102106
diameter=0.4,
103107
),
104108
Tubular(
105109
name="Brace4",
106110
axis=Axis3D(
107-
point=Point3D(x=-0.3, y=0.3, z=-1), vector=Vector3D(x=-2, y=2, z=-2)
111+
point=Point3D(x=-0.3, y=0.3, z=-1),
112+
vector=Vector3D(x=-2, y=2, z=-2),
108113
),
109114
diameter=0.4,
110115
),

src/app/modelling/geometry/intersections.py

+17-8
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def intersection(master: NpTubular, slave: NpTubular) -> np.ndarray:
7575

7676
return intersect3D
7777

78+
7879
def circle_intersect(
7980
center: np.ndarray, diameter: float, point: np.ndarray, vector: np.ndarray
8081
) -> np.ndarray:
@@ -143,31 +144,37 @@ def flat_tube_intersection(master: NpTubular, slave: NpTubular) -> np.ndarray:
143144
# Get intersections on master surface
144145
point = intersection(master, slave)
145146
master_circle = sympy.Circle(master.axis.point.array[:2], master.diameter / 2.0)
146-
angle = arc_angle_signed(master_circle, point) * -1.0 # -1 since rotation is X to Y
147+
angle = arc_angle_signed(master_circle, point) * -1.0 # -1 since rotation is X to Y
147148
if angle > math.pi:
148149
angle = 2 * math.pi - angle
149150
elif angle < -1 * math.pi:
150151
angle = -2 * math.pi - angle
151-
circ_length = (master_circle.circumference / 2.) * angle / math.pi
152+
circ_length = (master_circle.circumference / 2.0) * angle / math.pi
152153
point[0] = circ_length
154+
# Y coordinate is at tube radius
155+
point[1] = master.diameter / 2.0
153156
return point
154157

158+
155159
def arc_angle_signed(circle: sympy.Circle, point: np.ndarray) -> float:
156160
"""Seam (0 rads) is aligned with Y axis
157-
161+
158162
Positive rotation from X to Y axis (clockwise!)
159163
160164
Circle is in 2D X/Y plane
161165
"""
162-
seam = sympy.Point2D(0., circle.radius)
166+
seam = sympy.Point2D(0.0, circle.radius)
163167
seg_vector = np.empty((3,))
164168
seg_vector[:2] = point[:2] - np.array(seam.coordinates)
165169
seg_vector[2] = 0.0
166170
seg_length = np.linalg.norm(seg_vector)
167171
sub_angle = math.acos(seg_length / 2.0 / circle.radius)
168172
return np.sign(circle.center[0] - point[0]) * (math.pi - 2 * sub_angle)
169173

170-
def plane_intersect(axis: np.ndarray, point: np.ndarray, plane: sympy.Plane) -> np.ndarray:
174+
175+
def plane_intersect(
176+
axis: np.ndarray, point: np.ndarray, plane: sympy.Plane
177+
) -> np.ndarray:
171178
"""Calculate 3D point where slave intersects plane
172179
173180
Plane is in X/Z plane.
@@ -185,15 +192,17 @@ def plane_intersect(axis: np.ndarray, point: np.ndarray, plane: sympy.Plane) ->
185192
plane_point = np.array(plane.p1, dtype=float)
186193
plane_normal = np.array(plane.normal_vector, dtype=float)
187194
epsilon = 1e-6
188-
195+
189196
try:
190197
ndotu = plane_normal.dot(axis)
191198
if abs(ndotu) < epsilon:
192-
raise IntersectionError(f"Cannot compute intersect of vector {axis} which lies in plane {plane}")
199+
raise IntersectionError(
200+
f"Cannot compute intersect of vector {axis} which lies in plane {plane}"
201+
)
193202
w = point - plane_point
194203
si = -plane_normal.dot(w) / ndotu
195204
return w + si * axis + plane_point
196205
except Exception as e:
197206
# Handle some specific exception here where an intersection is not found
198207
msg = f"Could not find intersection point of vector {axis} with plane {plane}.\nEncountered error:\n{e}"
199-
raise IntersectionError(msg)
208+
raise IntersectionError(msg)

src/app/modelling/geometry/vectors.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,4 @@ def rotate(point: np.ndarray, axis: np.ndarray, angle: float) -> np.ndarray:
3030
rotation = transforms.rotation_matrix(angle, axis, point)
3131
rpoint = np.zeros((4,))
3232
rpoint[:3] = point[:3]
33-
return np.dot(rotation, rpoint)[:3]
33+
return np.dot(rotation, rpoint)[:3]

src/app/modelling/geometry/weld.py

+22-9
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,26 @@
44
from typing import Generator
55

66
from .vectors import rotate, unit_vector
7-
from .intersections import flat_tube_intersection, plane_intersect, intersection, arc_angle_signed
7+
from .intersections import (
8+
flat_tube_intersection,
9+
plane_intersect,
10+
intersection,
11+
arc_angle_signed,
12+
)
813
from ...interfaces import *
914

1015
# TODO: look into https://mathcurve.com/courbes2d.gb/alain/alain.shtml
1116
# TODO:https://math.stackexchange.com/questions/141593/formula-for-cylinder?newreg=52c6b3d9cb1d47c1aaa3ff0706aa2298
1217
# Plane is XZ so X = Z, Y = offset
1318

19+
1420
def get_weld_intersect_points(
1521
master: NpTubular, slave: NpTubular, angle_inc: int = 10
1622
) -> Generator[np.ndarray, np.ndarray, None]:
1723
"""X/Z plane"""
1824
intersect = intersection(master, slave)
1925
# Radius point is on Y axis at radius
20-
radius_point = np.array([0., master.diameter / 2., intersect[2]])
26+
radius_point = np.array([0.0, master.diameter / 2.0, intersect[2]])
2127
# Create X/Z plane at radius point
2228
p2 = deepcopy(radius_point)
2329
p2[0] += 1.0
@@ -31,24 +37,31 @@ def get_weld_intersect_points(
3137
radius_arc_angle = arc_angle_signed(circle, radius_point)
3238
intersect_arc_angle = arc_angle_signed(circle, intersect)
3339
arc_angle = radius_arc_angle + intersect_arc_angle
34-
slave_vector = rotate(unit_vector(slave.axis.vector.array), np.array([0.0, 0.0, 1.0]), arc_angle * -1.)
35-
perp = unit_vector(np.cross(master.axis.vector.array, slave_vector)) * slave.diameter / 2.
40+
slave_vector = rotate(
41+
unit_vector(slave.axis.vector.array),
42+
np.array([0.0, 0.0, 1.0]),
43+
arc_angle * -1.0,
44+
)
45+
perp = (
46+
unit_vector(np.cross(master.axis.vector.array, slave_vector))
47+
* slave.diameter
48+
/ 2.0
49+
)
3650

3751
# Get intersect on flattened tube plane
3852
flat_intersect = flat_tube_intersection(master, slave)
39-
flat_intersect[1] = radius_point[1]
4053

41-
def calc_angle(angle):
54+
def pnt_intersect_at_angle(angle):
4255
# calculate new vector and point
4356
rotated_point = flat_intersect + rotate(perp, slave_vector, angle)
4457
pnt_intersect = plane_intersect(slave_vector, rotated_point, plane)
4558
pnt_intersect = np.array(pnt_intersect, dtype=float)
4659
return pnt_intersect
4760

4861
for degrees in range(0, 361, angle_inc):
49-
angle = degrees * math.pi / 180.
62+
angle = degrees * math.pi / 180.0
5063
if abs(degrees - 360) < 1e-8 or degrees > 360:
5164
continue
52-
yield calc_angle(angle)
65+
yield pnt_intersect_at_angle(angle)
5366

54-
return calc_angle(0.0)
67+
return pnt_intersect_at_angle(0.0)

0 commit comments

Comments
 (0)