Skip to content

Commit 831b480

Browse files
committed
Refactor SemiCylinders/Joints and fix bugs from other parts
1 parent bd9bfbf commit 831b480

File tree

10 files changed

+160
-63
lines changed

10 files changed

+160
-63
lines changed

examples/assembly/n_joint.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import os
2+
3+
import classy_blocks as cb
4+
from classy_blocks.construct.assemblies.joints import NJoint
5+
6+
mesh = cb.Mesh()
7+
8+
axis_point_1 = [0.0, 0.0, 0.0]
9+
axis_point_2 = [5.0, 5.0, 0.0]
10+
radius_point_1 = [0.0, 0.0, 2.0]
11+
12+
# cylinder = cb.Cylinder(axis_point_1, axis_point_2, radius_point_1)
13+
14+
joint = NJoint([0, -1, 0], [0, 0, 0], [0.2, -1, 0], 3)
15+
16+
for operation in joint.operations:
17+
for i in range(3):
18+
operation.chop(i, count=10)
19+
20+
mesh.add(joint)
21+
22+
mesh.write(os.path.join("..", "case", "system", "blockMeshDict"), debug_path="debug.vtk")

src/classy_blocks/construct/array.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def shear(self, normal: VectorType, origin: PointType, direction: VectorType, an
7474
if f.point_to_plane_distance(origin, normal, point) > TOL:
7575
distance = f.point_to_plane_distance(origin, normal, point)
7676
direction = f.unit_vector(direction)
77-
amount = distance * np.tan(angle)
77+
amount = distance / np.tan(angle)
7878

7979
self.points[i] += direction * amount
8080
return self
Lines changed: 86 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,70 @@
1+
from typing import List
2+
13
import numpy as np
24

35
from classy_blocks.construct.assemblies.assembly import Assembly
4-
from classy_blocks.construct.shapes.cylinder import SemiCylinder, SlashedCylinder
6+
from classy_blocks.construct.edges import Spline
7+
from classy_blocks.construct.flat.sketches.disk import HalfDisk
8+
from classy_blocks.construct.shape import Shape
9+
from classy_blocks.construct.shapes.cylinder import SemiCylinder
510
from classy_blocks.types import PointType
611
from classy_blocks.util import functions as f
712

813

14+
class CuspSemiCylinder(SemiCylinder):
15+
sketch_class = HalfDisk
16+
17+
def __init__(
18+
self, axis_point_1: PointType, axis_point_2: PointType, radius_point_1: PointType, end_angle: float = np.pi / 4
19+
):
20+
axis_point_1 = np.asarray(axis_point_1)
21+
axis_point_2 = np.asarray(axis_point_2)
22+
axis = axis_point_2 - axis_point_1
23+
radius_point_1 = np.asarray(radius_point_1)
24+
radius_vector = radius_point_1 - axis_point_1
25+
26+
shear_normal = np.cross(-axis, radius_vector)
27+
28+
super().__init__(axis_point_1, axis_point_2, radius_point_1)
29+
30+
for loft in self.operations:
31+
loft.top_face.remove_edges()
32+
33+
for loft in self.shell:
34+
point_1 = loft.top_face.points[1].position
35+
point_2 = loft.top_face.points[2].position
36+
edge_points = f.divide_arc(axis, axis_point_2, point_1, point_2, 5)
37+
loft.top_face.add_edge(1, Spline(edge_points))
38+
39+
for loft in self.operations:
40+
loft.top_face.shear(shear_normal, axis_point_2, -axis, end_angle)
41+
42+
# TODO: include those inner edges (Disk.spline_ratios > Curve)
43+
# self.remove_inner_edges(end=True)
44+
45+
46+
class CuspCylinder(Assembly):
47+
def __init__(
48+
self,
49+
axis_point_1: PointType,
50+
axis_point_2: PointType,
51+
radius_point: PointType,
52+
end_angle_left: float,
53+
end_angle_right: float,
54+
):
55+
axis_point_1 = np.asarray(axis_point_1)
56+
radius_point_right = np.asarray(radius_point)
57+
radius_vector_right = np.asarray(radius_point) - axis_point_1
58+
radius_point_left = axis_point_1 - radius_vector_right
59+
60+
self.cusp_right = CuspSemiCylinder(axis_point_1, axis_point_2, radius_point_right, end_angle_right)
61+
self.cusp_left = CuspSemiCylinder(axis_point_1, axis_point_2, radius_point_left, end_angle_left)
62+
63+
@property
64+
def shapes(self):
65+
return [self.cusp_right, self.cusp_left]
66+
67+
968
class TJoint(Assembly):
1069
def __init__(self, start_point: PointType, center_point: PointType, right_point: PointType, radius: float):
1170
start_point = np.array(start_point)
@@ -19,21 +78,35 @@ def __init__(self, start_point: PointType, center_point: PointType, right_point:
1978

2079
radius_vector = radius * f.unit_vector(np.cross(right_vector, start_vector))
2180

22-
# the right part
23-
start_right_sc = SlashedCylinder(start_point, center_point, start_point + radius_vector)
24-
right_sc = SlashedCylinder(right_point, center_point, right_point - radius_vector)
81+
# middle
82+
self.middle = CuspCylinder(start_point, center_point, start_point + radius_vector, np.pi / 4, np.pi / 4)
83+
84+
# the right 'arm'
85+
self.right = CuspCylinder(right_point, center_point, right_point + radius_vector, np.pi / 4, np.pi / 2)
86+
87+
# the left 'arm'
88+
self.left = CuspCylinder(left_point, center_point, left_point - radius_vector, np.pi / 4, np.pi / 2)
89+
90+
super().__init__([*self.middle.shapes, *self.right.shapes, *self.left.shapes])
91+
2592

26-
# the left part
27-
start_left_sc = SlashedCylinder(start_point, center_point, start_point - radius_vector)
28-
left_sc = SlashedCylinder(left_point, center_point, left_point + radius_vector)
93+
class NJoint(Assembly):
94+
def __init__(self, start_point: PointType, center_point: PointType, radius_point: PointType, branches: int = 4):
95+
start_point = np.asarray(start_point)
96+
center_point = np.asarray(center_point)
97+
radius_point = np.asarray(radius_point)
2998

30-
# the top part
31-
top_right = SemiCylinder(center_point, right_point, center_point - radius_vector)
32-
top_left = SemiCylinder(center_point, left_point, center_point + radius_vector)
99+
self.assemblies: List[Assembly] = []
100+
shapes: List[Shape] = []
33101

34-
shapes = [start_right_sc, right_sc, start_left_sc, left_sc, top_right, top_left]
102+
cusp_angle = np.pi / branches
103+
base_asm = CuspCylinder(start_point, center_point, radius_point, cusp_angle, cusp_angle)
104+
rotate_angles = np.linspace(0, 2 * np.pi, num=branches, endpoint=False)
105+
rotation_axis = radius_point - start_point
35106

36-
for shape in shapes:
37-
shape.remove_inner_edges(end=True)
107+
for angle in rotate_angles:
108+
asm = base_asm.copy().rotate(angle, rotation_axis, center_point)
109+
self.assemblies.append(asm)
110+
shapes += asm.shapes
38111

39112
super().__init__(shapes)

src/classy_blocks/construct/shapes/cylinder.py

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -39,34 +39,6 @@ def __init__(self, axis_point_1: PointType, axis_point_2: PointType, radius_poin
3939
super().__init__(self.sketch_class(axis_point_1, radius_point_1, axis), transform_2, None)
4040

4141

42-
class SlashedCylinder(RoundSolidShape):
43-
sketch_class = HalfDisk
44-
45-
def __init__(
46-
self, axis_point_1: PointType, axis_point_2: PointType, radius_point_1: PointType, end_angle: float = np.pi / 4
47-
):
48-
axis_point_1 = np.asarray(axis_point_1)
49-
axis_point_2 = np.asarray(axis_point_2)
50-
axis = axis_point_2 - axis_point_1
51-
radius_point_1 = np.asarray(radius_point_1)
52-
radius_vector = radius_point_1 - axis_point_1
53-
54-
shear_normal = np.cross(-axis, radius_vector)
55-
56-
diff = np.dot(axis, radius_point_1 - axis_point_1)
57-
if diff > TOL:
58-
raise CylinderCreationError(
59-
"Axis and radius vectors are not perpendicular", f"Difference: {diff}, tolerance: {TOL}"
60-
)
61-
62-
transform_2: List[tr.Transformation] = [
63-
tr.Translation(axis),
64-
tr.Shear(shear_normal, axis_point_2, -axis, end_angle),
65-
]
66-
67-
super().__init__(self.sketch_class(axis_point_1, radius_point_1, axis), transform_2, None)
68-
69-
7042
class Cylinder(SemiCylinder):
7143
sketch_class = Disk
7244

src/classy_blocks/construct/shapes/sphere.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -101,16 +101,11 @@ class EighthSphere(Shape):
101101
def __init__(
102102
self, center_point: PointType, radius_point: PointType, normal: VectorType, diagonal_angle: float = np.pi / 5
103103
):
104-
# TODO: move these to properties
105-
# BUG: move these to properties
106-
# (will not change with transforms!)
107-
self.center_point = np.asarray(center_point)
108-
self.radius_point = np.asarray(radius_point)
109-
self.normal = f.unit_vector(np.asarray(normal))
104+
center_point = np.asarray(center_point)
105+
radius_point = np.asarray(radius_point)
106+
normal = f.unit_vector(np.asarray(normal))
110107

111-
self.lofts = eighth_sphere_lofts(
112-
self.center_point, self.radius_point, self.normal, self.geometry_label, diagonal_angle
113-
)
108+
self.lofts = eighth_sphere_lofts(center_point, radius_point, normal, self.geometry_label, diagonal_angle)
114109

115110
### Chopping
116111
def chop_axial(self, **kwargs):
@@ -156,6 +151,18 @@ def shell(self):
156151
def grid(self):
157152
return [self.core, self.shell]
158153

154+
@property
155+
def radius_point(self) -> NPPointType:
156+
return self.lofts[1].bottom_face.points[1].position
157+
158+
@property
159+
def center_point(self) -> NPPointType:
160+
return self.lofts[0].bottom_face.points[0].position
161+
162+
@property
163+
def normal(self) -> NPVectorType:
164+
return self.lofts[0].bottom_face.normal
165+
159166
@property
160167
def radius(self) -> float:
161168
"""Radius of this sphere"""
@@ -166,6 +173,10 @@ def geometry_label(self) -> str:
166173
"""Name of a unique geometry this will project to"""
167174
return f"sphere_{id(self)}"
168175

176+
@property
177+
def center(self):
178+
return self.center_point
179+
169180
@property
170181
def geometry(self):
171182
return {

src/classy_blocks/items/edges/arcs/angle.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,8 @@ def arc_from_theta(edge_point_1: PointType, edge_point_2: PointType, angle: floa
3636
mag_chord = f.norm(chord)
3737

3838
center = pm - length * axis / 2 - rm * mag_chord / 2 / np.tan(angle / 2)
39-
radius = f.norm(edge_point_1 - center)
4039

41-
return f.arc_mid(axis, center, radius, edge_point_1, edge_point_2)
40+
return f.arc_mid(axis, center, edge_point_1, edge_point_2)
4241

4342

4443
@dataclasses.dataclass

src/classy_blocks/items/edges/arcs/origin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def arc_from_origin(
7373
return arc_from_origin(p1, p3, new_center, False)
7474

7575
# done, return the calculated point
76-
return f.arc_mid(axis, center, radius, edge_point_1, edge_point_2)
76+
return f.arc_mid(axis, center, edge_point_1, edge_point_2)
7777

7878

7979
@dataclasses.dataclass

src/classy_blocks/util/functions.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -201,19 +201,27 @@ def arc_length_3point(p_start: NPPointType, p_btw: NPPointType, p_end: NPPointTy
201201
return angle * norm(radius)
202202

203203

204-
def arc_mid(axis: VectorType, center: PointType, radius: float, point_1: PointType, point_2: PointType) -> PointType:
205-
"""Returns the midpoint of the specified arc in 3D space"""
204+
def divide_arc(
205+
axis: VectorType, center: PointType, point_1: PointType, point_2: PointType, count: int
206+
) -> NPPointListType:
206207
# Kudos to this guy for his shrewd solution
207208
# https://math.stackexchange.com/questions/3717427
209+
# (extended here to create more than 1 point)
208210
axis = np.asarray(axis, dtype=constants.DTYPE)
209211
center = np.asarray(center, dtype=constants.DTYPE)
210212
point_1 = np.asarray(point_1, dtype=constants.DTYPE)
211213
point_2 = np.asarray(point_2, dtype=constants.DTYPE)
214+
radius = norm(center - point_1)
215+
216+
secant_points = np.linspace(point_1, point_2, num=count + 2)[1:-1]
217+
secant_vectors = [unit_vector(point - center) for point in secant_points]
212218

213-
sec = point_2 - point_1
214-
sec_ort = np.cross(sec, axis)
219+
return np.array([center + vector * radius for vector in secant_vectors])
215220

216-
return center + unit_vector(sec_ort) * radius
221+
222+
def arc_mid(axis: VectorType, center: PointType, point_1: PointType, point_2: PointType) -> PointType:
223+
"""Returns the midpoint of the specified arc in 3D space"""
224+
return divide_arc(axis, center, point_1, point_2, 1)[0]
217225

218226

219227
def mirror_matrix(normal: VectorType):

tests/test_construct/test_curves/test_interpolated.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,21 @@ def test_transform(self):
6868
def test_param_at_length(self):
6969
self.assertAlmostEqual(self.curve.get_param_at_length(1), 0.25)
7070

71+
def test_shear(self):
72+
curve = self.curve
73+
74+
curve.shear([0, 1, 0], [0, 0, 0], [1, 0, 0], np.pi / 4)
75+
76+
expected = [
77+
[0, 0, 0],
78+
[1, 1, 0],
79+
[2, 1, 0],
80+
[1, 0, 0],
81+
[2, 0, 0],
82+
]
83+
84+
np.testing.assert_almost_equal(curve.discretize(count=5), expected)
85+
7186

7287
class SplineInterpolatedCurveTests(unittest.TestCase):
7388
def setUp(self):

tests/test_items/test_edge.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -269,13 +269,10 @@ class AlternativeArcTests(unittest.TestCase):
269269
def test_arc_mid(self):
270270
axis = f.vector(0, 0, 1)
271271
center = f.vector(0, 0, 0)
272-
radius = 1
273272
edge_point_1 = f.vector(1, 0, 0)
274273
edge_point_2 = f.vector(0, 1, 0)
275274

276-
np.testing.assert_array_almost_equal(
277-
f.arc_mid(axis, center, radius, edge_point_1, edge_point_2), self.unit_sq_corner
278-
)
275+
np.testing.assert_array_almost_equal(f.arc_mid(axis, center, edge_point_1, edge_point_2), self.unit_sq_corner)
279276

280277
def test_arc_from_theta(self):
281278
edge_point_1 = f.vector(0, 1, 0)

0 commit comments

Comments
 (0)