├── .gitignore
├── unfold_pyramid
├── sketch_190502a.gif
└── unfold_pyramid.py
├── extruded_simple_poly
├── sketch_190917b.gif
├── extruded_simple_poly.py
└── parts.py
├── paraboloid_box_v0
├── paraboloid_box_v0.png
├── debug.py
├── draw_3D.py
├── paraboloid_box_v0.py
└── draw_2D.py
├── box_with_circular_holes
├── sketch_190918a.gif
├── box_with_circular_holes.py
└── frame_box.py
├── box_with_rectangular_holes
├── sketch_190522a.gif
├── box_with_rectangular_holes.py
└── frame_box.py
├── unfold_pyramidal_solid_py5
├── sketch_190509a.gif
├── unfold_pyramidal_solid.py
└── geometry.py
├── simple_2D_unfolded_box
├── simple_2D_unfolded_box.png
└── simple_2D_unfolded_box.py
├── .github
└── FUNDING.yml
├── _layouts
└── default.html
├── README.md
└── LICENSE.md
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | *.class
3 | *.DS_Store
4 | *.properties
5 |
--------------------------------------------------------------------------------
/unfold_pyramid/sketch_190502a.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/villares/Paper-objects-with-Processing-and-Python/HEAD/unfold_pyramid/sketch_190502a.gif
--------------------------------------------------------------------------------
/extruded_simple_poly/sketch_190917b.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/villares/Paper-objects-with-Processing-and-Python/HEAD/extruded_simple_poly/sketch_190917b.gif
--------------------------------------------------------------------------------
/paraboloid_box_v0/paraboloid_box_v0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/villares/Paper-objects-with-Processing-and-Python/HEAD/paraboloid_box_v0/paraboloid_box_v0.png
--------------------------------------------------------------------------------
/box_with_circular_holes/sketch_190918a.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/villares/Paper-objects-with-Processing-and-Python/HEAD/box_with_circular_holes/sketch_190918a.gif
--------------------------------------------------------------------------------
/box_with_rectangular_holes/sketch_190522a.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/villares/Paper-objects-with-Processing-and-Python/HEAD/box_with_rectangular_holes/sketch_190522a.gif
--------------------------------------------------------------------------------
/unfold_pyramidal_solid_py5/sketch_190509a.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/villares/Paper-objects-with-Processing-and-Python/HEAD/unfold_pyramidal_solid_py5/sketch_190509a.gif
--------------------------------------------------------------------------------
/simple_2D_unfolded_box/simple_2D_unfolded_box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/villares/Paper-objects-with-Processing-and-Python/HEAD/simple_2D_unfolded_box/simple_2D_unfolded_box.png
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 |
2 | liberapay: villares
3 | # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
4 | custom: ['https://wise.com/pay/me/alexandrev562', 'https://www.paypal.com/donate/?hosted_button_id=5B4MZ78C9J724']
5 |
--------------------------------------------------------------------------------
/paraboloid_box_v0/debug.py:
--------------------------------------------------------------------------------
1 | DEBUG = False
2 |
3 | def debug_text(name, points, enum=False):
4 | if DEBUG:
5 | for i, p in enumerate(points):
6 | with push():
7 |
8 | fill(255, 0, 0)
9 | if enum:
10 | translate(0, -5, 10)
11 | text(name + "-" + str(i), *p)
12 | else:
13 | translate(10, 10, 10)
14 | text(name[i], *p)
15 |
--------------------------------------------------------------------------------
/_layouts/default.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Alexandre Villares
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | {{ content }}
17 |
18 |
19 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/box_with_circular_holes/box_with_circular_holes.py:
--------------------------------------------------------------------------------
1 | """
2 | Alexandre B A Villares - https://abav.lugaralgum.com/sketch-a-day
3 |
4 | Box with circular holes
5 |
6 | based on https://github.com/villares/sketch-a-day/tree/master/2019/sketch_190918a)
7 | """
8 |
9 | from frame_box import frame_box, unfolded_frame_box
10 |
11 | DIMENSION_KEYS = (('a', 'd'),
12 | ('w', 's'),
13 | (LEFT, RIGHT),
14 | (UP, DOWN))
15 | dimensions = [250, 150, 100, 30]
16 | modes = [-1, 0, 1] # click mouse to switch modes
17 |
18 | def setup():
19 | size(600, 600, P3D)
20 |
21 | def draw():
22 | background(200)
23 | translate(300, 300)
24 | w, h, d, thick = dimensions
25 | if modes[0] >= 0:
26 | fill(255)
27 | stroke(0)
28 | push_matrix()
29 | translate(0, 0, 200)
30 | rotate_x(HALF_PI / 2)
31 | frame_box(w, h, d, thick)
32 | pop_matrix()
33 | if modes[0] <= 0:
34 | unfolded_frame_box(w, h, d, thick)
35 |
36 | def mouse_pressed():
37 | modes[:] = modes[1:] + [modes[0]]
38 |
39 | def key_pressed():
40 | if key == 'p':
41 | save_frame("a###.png")
42 | if key == ' ':
43 | dimensions[:] = [250, 150, 100, 30]
44 |
45 | k = key_code if key == CODED else key
46 | for i, (plus, minus) in enumerate(DIMENSION_KEYS):
47 | if k == plus:
48 | dimensions[i] += 1
49 | elif k == minus:
50 | dimensions[i] -= 1
51 |
--------------------------------------------------------------------------------
/box_with_rectangular_holes/box_with_rectangular_holes.py:
--------------------------------------------------------------------------------
1 | """
2 | Alexandre B A Villares - https://abav.lugaralgum.com/sketch-a-day
3 |
4 | Box with rectangular holes
5 | """
6 |
7 | from frame_box import frame_box, unfolded_frame_box
8 |
9 | DIMENSION_KEYS = (('a', 'd'),
10 | ('w', 's'),
11 | (LEFT, RIGHT),
12 | (UP, DOWN))
13 | modes = [-1, 0, 1] # click mouse to switch modes
14 | dimensions = [250, 150, 100, 30] # initial dimensions, a list to be mutated
15 |
16 | def setup():
17 | size(600, 600, P3D)
18 |
19 | def draw():
20 | background(200)
21 | translate(300, 300)
22 | w, h, d, thick = dimensions
23 | if modes[0] >= 0:
24 | fill(255)
25 | stroke(0)
26 | push_matrix()
27 | translate(0, 0, 200)
28 | rotate_x(HALF_PI / 2)
29 | frame_box(w, h, d, thick)
30 | pop_matrix()
31 |
32 | if modes[0] <= 0:
33 | unfolded_frame_box(w, h, d, thick)
34 |
35 | if is_key_pressed:
36 | k = key_code if key == CODED else key
37 | for i, (plus, minus) in enumerate(DIMENSION_KEYS):
38 | if k == plus:
39 | dimensions[i] += 1
40 | elif k == minus:
41 | dimensions[i] -= 1
42 |
43 | def mouse_pressed():
44 | modes[:] = modes[1:] + [modes[0]]
45 |
46 | def key_pressed():
47 | if key == 's':
48 | save_frame("a###.png")
49 | if key == ' ':
50 | dimensions[:] = [250, 150, 100, 30]
51 |
52 |
--------------------------------------------------------------------------------
/extruded_simple_poly/extruded_simple_poly.py:
--------------------------------------------------------------------------------
1 | # Alexandre B A Villares - https://abav.lugaralgum.com/sketch-a-day
2 | """
3 | Unfold simple extrusion (no holes)
4 | """
5 |
6 | from parts import Face, glue_tab
7 |
8 | faces = []
9 | THICK = 20
10 |
11 | def setup():
12 | size(740, 480, P3D)
13 | zig = [(2.5, 3.5), (5.5, 3.5), (2.5, 5.5),
14 | # (5.5, 5.5), (2.5, 7.5), (5.5, 7.5),
15 | # (3.5, 9.5), (8.5, 6.5), (5.5, 6.5),
16 | (8.5, 4.5), (5.5, 4.5), (8.5, 2.5),
17 | # (5.5, 2.5), (7.5, 0.5)
18 | ]
19 | faces.append(Face(zig, THICK))
20 |
21 |
22 | def draw():
23 | background(200, 210, 220)
24 | for f in faces:
25 | fill(255)
26 | stroke(0)
27 | f.draw_3D(-QUARTER_PI)
28 | no_fill()
29 | stroke(255, 0, 0)
30 | translate(200, 0)
31 | f.draw_2D()
32 | translate(200, 0)
33 | f.draw_2D()
34 | x, y = 25, 350
35 | translate(-400, 0)
36 | for p1, p2 in f.edges():
37 | d = dist(p1[0], p1[1], p2[0], p2[1]) * 35
38 | glue_tab((x, y), (x + d, y))
39 | glue_tab((x + d, y + THICK),( x, y + THICK))
40 | stroke(0, 0, 255)
41 | rect(x, y , d, THICK)
42 | x += d
43 | stroke(255, 0, 0)
44 | if x > width - d:
45 | glue_tab((x, y), (x, y + THICK))
46 | x = 25
47 | y += THICK * 2.2
48 | else: # a for else...
49 | glue_tab((x, y), (x, y + THICK))
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Paper objects with Processing and Python
2 |
3 |
4 |
5 | Studies in digital fabrication [from this GitHub repository](https://github.com/villares/Paper-objects-with-Processing-and-Python/) using the [py5](https://py5coding.org) library that combines the Processing drawing infrastructure with modern Python. More about [installing py5 and using the imported mode style](https://abav.lugaralgum.com/como-instalar-py5/index-EN.html).
6 |
7 | For the earlier *Processing Python Mode* version of this project, visit the ["archived branch"](https://github.com/villares/Paper-objects-with-Processing-and-Python/tree/Processing-Python-mode).
8 |
9 |
12 |
13 | If you like this, support my work with a small donation!
14 | [Here](https://www.paypal.com/donate/?hosted_button_id=5B4MZ78C9J724), with [Wise](https://wise.com/pay/me/alexandrev562) or at [LiberaPay](https://liberapay.com/Villares).
15 |
16 | ## Simple 2D unfolded box
17 |
18 | 
19 |
20 | [Code for the Simple 2D unfolded box](https://github.com/villares/Paper-objects-with-Processing-and-Python/tree/main/simple_2D_unfolded_box/)
21 |
22 | ## Box with rectangular holes
23 |
24 | 
25 |
26 | [Code for Box with rectangular holes](https://github.com/villares/Paper-objects-with-Processing-and-Python/tree/main/box_with_rectangular_holes/)
27 |
28 | ## Box with cricular holes
29 |
30 | 
31 |
32 | [Code for Box with circular holes](https://github.com/villares/Paper-objects-with-Processing-and-Python/tree/main/box_with_circular_holes/)
33 |
34 | ## Paraboloid box
35 |
36 | 
37 |
38 | [Code for Paraboloid box v0](https://github.com/villares/Paper-objects-with-Processing-and-Python/tree/main/paraboloid_box_v0/)
39 |
40 | ## Unfold Pyramid
41 |
42 | 
43 |
44 | [Code for Unfold pyramid](https://github.com/villares/Paper-objects-with-Processing-and-Python/tree/main/unfold_pyramid/)
45 |
46 | ## Unfold Pyramidal Solid
47 |
48 | 
49 |
50 | [Code for Unfold pyramidal solid](https://github.com/villares/Paper-objects-with-Processing-and-Python/tree/main/unfold_pyramidal_solid_py5/)
51 |
52 |
--------------------------------------------------------------------------------
/simple_2D_unfolded_box/simple_2D_unfolded_box.py:
--------------------------------------------------------------------------------
1 | CUT_STROKE, FOLD_STROKE = color(255, 0, 0), color(0, 0, 255)
2 |
3 |
4 | def setup():
5 | size(600, 600, P3D)
6 |
7 |
8 | def draw():
9 | background(200)
10 | translate(300, 300)
11 | unfolded_box(250, 150, 100)
12 |
13 |
14 | def unfolded_box(w, h, d):
15 | mw, mh, md = w/2., h/2., d/2.
16 | face_2_d(0, -h - md, w, d, "aaan")
17 | face_2_d(0, -mh, w, h, "vvvv")
18 | face_2_d(0, -mh + mh + md, w, d, "cncv")
19 | face_2_d(0, +mh + d, w, h, "cncc")
20 | face_2_d(-mw - md, -mh, d, h, "acna")
21 | face_2_d(mw + md, -mh, d, h, "ncaa")
22 |
23 |
24 | def face_2_d(x, y, w, h, lados):
25 | """
26 | O string lados:
27 | a: aba (tab)
28 | c: corte (cut)
29 | v: vinco (fold)
30 | n: não (no line)
31 | """
32 | l0, l1, l2, l3 = lados
33 | mw, mh = w/2., h/2.
34 | push_matrix()
35 | translate(x, y)
36 | my_line(-mw, +mh, -mw, -mh, l0)
37 | my_line(-mw, -mh, +mw, -mh, l1)
38 | my_line(+mw, -mh, +mw, +mh, l2)
39 | my_line(+mw, +mh, -mw, +mh, l3)
40 | pop_matrix()
41 |
42 |
43 | def my_line(x0, y0, x1, y1, variation):
44 | if variation == "n":
45 | return
46 | elif variation == "c":
47 | stroke(CUT_STROKE)
48 | else:
49 | stroke(FOLD_STROKE)
50 | line(x0, y0, x1, y1)
51 | if variation == "a":
52 | stroke(CUT_STROKE)
53 | no_fill()
54 | glue_tab((x0, y0), (x1, y1), 10)
55 |
56 |
57 | def glue_tab(p1, p2, tab_w, cut_ang=QUARTER_PI/3):
58 | """
59 | draws a trapezoidal or triangular glue tab along edge defined by p1 and p2,
60 | with width tab_w and cut angle a
61 | """
62 | al = atan2(p1[0] - p2[0], p1[1] - p2[1])
63 | a1 = al + cut_ang + PI
64 | a2 = al - cut_ang
65 | # calculate cut_len to get the right tab width
66 | cut_len = tab_w / sin(cut_ang)
67 | f1 = (p1[0] + cut_len * sin(a1),
68 | p1[1] + cut_len * cos(a1))
69 | f2 = (p2[0] + cut_len * sin(a2),
70 | p2[1] + cut_len * cos(a2))
71 | edge_len = dist(p1[0], p1[1], p2[0], p2[1])
72 |
73 | if edge_len > 2 * cut_len * cos(cut_ang): # 'normal' trapezoidal tab
74 | begin_shape()
75 | vertex(*p1) # vertex(p1[0], p1[1])
76 | vertex(*f1)
77 | vertex(*f2)
78 | vertex(*p2)
79 | end_shape()
80 | else: # short triangular tab
81 | fm = ((f1[0] + f2[0]) / 2, (f1[1] + f2[1]) / 2)
82 | begin_shape()
83 | vertex(*p1)
84 | vertex(*fm) # middle way of f1 and f2
85 | vertex(*p2)
86 | end_shape()
87 |
--------------------------------------------------------------------------------
/paraboloid_box_v0/draw_3D.py:
--------------------------------------------------------------------------------
1 | import py5
2 |
3 | from debug import debug_text
4 |
5 | def draw_3D(box_w, box_d, ab_l, cd_l):
6 | """
7 | main 3D drawing procedure, this also calculates some 3D point positions
8 | from 2 lists of heights (ab_l and cd_l) that will then be returned
9 | and used by the 2D procedure
10 | """
11 | # calculates upper 3D points from heights
12 | num_pts = len(cd_l)
13 | cd_3_dpts = tuple([(box_w,
14 | box_d - box_d * i / (num_pts - 1),
15 | cd_l[::-1][i]) for i in range(num_pts)])
16 | ab_3_dpts = tuple([(0, box_d * i / (num_pts - 1), ab_l[::-1][i])
17 | for i in range(num_pts)])
18 | # draw faces
19 | py5.stroke(0)
20 | py5.fill(255, 200)
21 | # floor face
22 | poly_draw(((0, 0, 0),
23 | (box_w, 0, 0),
24 | (box_w, box_d, 0),
25 | (0, box_d, 0)))
26 | # face 0
27 | poly_draw(((0, 0, ab_l[-1]),
28 | (box_w, 0, cd_l[0]),
29 | (box_w, 0, 0),
30 | (0, 0, 0)))
31 | # face 1
32 | poly_draw(cd_3_dpts + (
33 | (box_w, 0, 0),
34 | (box_w, box_d, 0)))
35 | # face 2
36 | poly_draw(((box_w, box_d, cd_l[-1]),
37 | (0, box_d, ab_l[0]),
38 | (0, box_d, 0),
39 | (box_w, box_d, 0)))
40 | # face 3
41 | poly_draw(ab_3_dpts + (
42 | (0, box_d, 0),
43 | (0, 0, 0)))
44 | # top faces - using calculated the 3D points
45 | face_data = []
46 | for i in range(1, len(ab_3_dpts)):
47 | p = i - 1
48 | a = py5.Py5Vector(*ab_3_dpts[i])
49 | b = py5.Py5Vector(*ab_3_dpts[p])
50 | c = py5.Py5Vector(*cd_3_dpts[::-1][p])
51 | d = py5.Py5Vector(*cd_3_dpts[::-1][i])
52 | triangulated_face(a, b, c, d)
53 | face_data.append((a, b, c, d))
54 | # debug text
55 | debug_text("cd", cd_3_dpts[::-1], enum=True)
56 | debug_text("ab", ab_3_dpts[::-1], enum=True)
57 | debug_text("DAad", ((box_w, box_d, cd_l[-1]),
58 | (0, box_d, ab_l[0]),
59 | (0, box_d, 0),
60 | (box_w, box_d, 0)))
61 | return face_data # returns to be used by the 2D procedure
62 |
63 |
64 | def poly_draw(pts, closed=True):
65 | """ sugar for face drawing """
66 | py5.begin_shape()
67 | for p in pts:
68 | py5.vertex(*p)
69 | if closed:
70 | py5.end_shape(py5.CLOSE)
71 | else:
72 | py5.end_shape()
73 |
74 |
75 | def triangulated_face(a, b, c, d):
76 | # two triangles - could be with a diferent diagonal!
77 | # TODO: let one choose diagonal orientation
78 | poly_draw((a, b, d))
79 | poly_draw((b, d, c))
80 |
81 |
--------------------------------------------------------------------------------
/extruded_simple_poly/parts.py:
--------------------------------------------------------------------------------
1 | import py5
2 |
3 |
4 | class Face:
5 |
6 | def __init__(self, pts, thickness, orientation=(0, 0, 0)):
7 | self.points = pts
8 | self.thickness = thickness
9 | self.o = orientation
10 |
11 | def draw_2D(self):
12 | draw_poly(self.points)
13 |
14 | def draw_3D(self, rot):
15 | S = 35.28
16 |
17 | t = self.thickness
18 | pts = self.points
19 | with py5.push_matrix():
20 | py5.translate(0, py5.height() / 2)
21 | py5.rotate_x(self.o[0] * 0 + rot)
22 | py5.translate(0, -py5.height() / 2)
23 | py5.translate(0, 0, -t/2)
24 | py5.fill(230)
25 | draw_poly(pts)
26 | py5.translate(0, 0, t)
27 | py5.fill(170)
28 | draw_poly(pts)
29 | py5.fill(250)
30 | for p1, p2 in pairwise(tuple(pts) + (pts[0],)):
31 | # print((p1, p2))
32 | py5.begin_shape(py5.QUAD_STRIP)
33 | py5.vertex(p1[0]*S, p1[1]*S, 0)
34 | py5.vertex(p1[0]*S, p1[1]*S, -t)
35 | py5.vertex(p2[0]*S, p2[1]*S, 0)
36 | py5.vertex(p2[0]*S, p2[1]*S, -t)
37 | py5.end_shape()
38 |
39 | def edges(self):
40 | return pairwise(tuple(self.points) + (self.points[0],))
41 |
42 |
43 | def draw_poly(pts, closed=True):
44 | S = 35.28
45 | py5.begin_shape()
46 | for p in pts:
47 | py5.vertex(p[0]*S, p[1]*S, 0)
48 | if closed:
49 | py5.end_shape(py5.CLOSE)
50 | else:
51 | py5.end_shape()
52 |
53 |
54 | def pairwise(iterable):
55 | import itertools
56 | "s -> (s0,s1), (s1,s2), (s2, s3), ..."
57 | a, b = itertools.tee(iterable)
58 | next(b, None)
59 | return zip(a, b)
60 |
61 |
62 | def glue_tab(p1, p2, tab_w=10, cut_ang=py5.QUARTER_PI/2):
63 | """
64 | draws a trapezoidal or triangular glue tab
65 | along edge defined by p1 and p2, with provided
66 | width (tab_w) and cut angle (cut_ang)
67 | """
68 | a1 = py5.atan2(p1[0] - p2[0], p1[1] - p2[1]) + cut_ang + py5.PI
69 | a2 = py5.atan2(p1[0] - p2[0], p1[1] - p2[1]) - cut_ang
70 | # calculate cut_len to get the right tab width
71 | cut_len = tab_w / py5.sin(cut_ang)
72 | f1 = (p1[0] + cut_len * py5.sin(a1),
73 | p1[1] + cut_len * py5.cos(a1))
74 | f2 = (p2[0] + cut_len * py5.sin(a2),
75 | p2[1] + cut_len * py5.cos(a2))
76 | edge_len = py5.dist(p1[0], p1[1], p2[0], p2[1])
77 |
78 | if edge_len > 2 * cut_len * py5.cos(cut_ang): # 'normal' trapezoidal tab
79 | with py5.begin_shape():
80 | py5.vertex(*p1) # vertex(p1[0], p1[1])
81 | py5.vertex(*f1)
82 | py5.vertex(*f2)
83 | py5.vertex(*p2)
84 | else: # short triangular tab
85 | fm = ((f1[0] + f2[0]) / 2, (f1[1] + f2[1]) / 2)
86 | with py5.begin_shape():
87 | py5.vertex(*p1)
88 | py5.vertex(*fm) # middle way of f1 and f2
89 | py5.vertex(*p2)
90 |
91 |
92 |
--------------------------------------------------------------------------------
/paraboloid_box_v0/paraboloid_box_v0.py:
--------------------------------------------------------------------------------
1 | """
2 | Alexandre B A Villares - https://abav.lugaralgum.com/sketch-a-day
3 |
4 | A paraboloid test
5 | """
6 | # add_library('peasycam')
7 |
8 | from draw_2D import draw_unfolded
9 | from draw_3D import draw_3D
10 |
11 | # initial box dimensions
12 | box_d, box_w, box_h = 100, 100, 100
13 | # height of points between d and c
14 | edge_num = 8 # number of points on top lateral edges
15 | cd_l = [box_h - 10 * i for i in range(edge_num)]
16 | # height of points between a and b
17 | ab_l = [box_h - 10 * i for i in range(edge_num)]
18 | assert len(cd_l) == len(ab_l) # equal number of points only
19 |
20 |
21 | def setup():
22 | size(1000, 720, P3D)
23 | global cam, export
24 | #cam = PeasyCam(this, -450, 0, 0, 300)
25 | # hint(ENABLE_DEPTH_SORT)
26 | smooth(16)
27 | stroke_weight(2)
28 | export = False
29 |
30 |
31 | def draw():
32 | global export
33 | background(200)
34 | # Draw 3D
35 | with push_matrix():
36 | # Comment out if using with PeasyCam
37 | translate(width / 2, height / 2)
38 | translate(50, 0)
39 | rotate_x(QUARTER_PI)
40 | translate(-400, -50, -50)
41 | # rotateZ(0)
42 | face_3D_data = draw_3D(box_w, box_d, ab_l, cd_l)
43 |
44 | # Draw 2D unfolded
45 | if export:
46 | begin_record(PDF, SKETCH_NAME + ".pdf")
47 | # cam.begin_hud() # for use with PeasyCam
48 | with push_matrix():
49 | translate(width * 0.66, 450)
50 | rotate(-HALF_PI)
51 | if export:
52 | scale(10, 10)
53 | draw_unfolded(box_w, box_d, ab_l, cd_l, face_3D_data)
54 | # cam.end_hud()
55 | if export:
56 | end_record()
57 | export = False
58 |
59 |
60 | def key_pressed():
61 | global box_w, box_d, box_h
62 | global export
63 |
64 | ah, bh, ch, dh = ab_l[0], ab_l[-1], cd_l[0], cd_l[-1]
65 | if key == "q":
66 | ah += 5
67 | if key == "a" and ah > 5:
68 | ah -= 5
69 | if key == "w":
70 | bh += 5
71 | if key == "s" and bh > 5:
72 | bh -= 5
73 | if key == "e":
74 | ch += 5
75 | if key == "d" and ch > 5:
76 | ch -= 5
77 | if key == "r":
78 | dh += 5
79 | if key == "f" and dh > 5:
80 | dh -= 5
81 | if key_code == UP and box_d + box_w < 220:
82 | box_d += 5
83 | if key_code == DOWN and box_d > 5:
84 | box_d -= 5
85 | if key_code == RIGHT and box_w + box_d < 220:
86 | box_w += 5
87 | if key_code == LEFT and box_w > 5:
88 | box_w -= 5
89 |
90 | ab_l[0], ab_l[-1], cd_l[0], cd_l[-1] = ah, bh, ch, dh
91 |
92 | if key == "S":
93 | print("file exported")
94 | export = True
95 | if key == "p":
96 | save_frame("####.png")
97 | elif key in ("+", "="):
98 | box_h += 5
99 | cd_l[:] = [cd_l[i] + 5 for i in range(edge_num)]
100 | ab_l[:] = [ab_l[i] + 5 for i in range(edge_num)]
101 | elif (key == "-" and box_h > 5 and ah > 5 and bh > 5 and ch > 5 and dh > 5):
102 | box_h -= 5
103 | cd_l[:] = [cd_l[i] - 5 for i in range(edge_num)]
104 | ab_l[:] = [ab_l[i] - 5 for i in range(edge_num)]
105 | elif key == " ":
106 | slowly_reset_values()
107 |
108 |
109 | def slowly_reset_values():
110 | global box_w, box_d, box_h, ah, bh, ch, dh
111 |
112 | box_w += (100 - box_w) / 2.
113 | box_d += (100 - box_d) / 2.
114 | delta_h = (100 - box_h) / 2.
115 | box_h += delta_h
116 | cd_l[:] = [cd_l[i] + delta_h for i in range(edge_num)]
117 | ab_l[:] = [ab_l[i] + delta_h for i in range(edge_num)]
118 |
119 |
--------------------------------------------------------------------------------
/unfold_pyramidal_solid_py5/unfold_pyramidal_solid.py:
--------------------------------------------------------------------------------
1 | """
2 | Alexandre B A Aillares - https://abav.lugaralgum.com/
3 |
4 | Unfolding a prism or pyramideal solid
5 |
6 | https://github.com/villares/paper-objects-with-processing-and-python
7 | Now for py5 (py5coding.org) imported mode
8 | """
9 |
10 | from geometry import poly_draw, line_draw, unfold_tri_face, glue_tab
11 |
12 | CUT_STROKE = color(255, 0, 0)
13 | FOLD_STROKE = color(0, 0, 255)
14 |
15 | p_height = 100
16 | base_radius, top_radius = 50, 50
17 | sides = 5
18 |
19 | def setup():
20 | size(600, 600, P3D)
21 | hint(ENABLE_DEPTH_TEST)
22 | hint(ENABLE_DEPTH_SORT)
23 |
24 | def draw():
25 | background(240)
26 | push_matrix()
27 | translate(width / 2, height / 4 + 50)
28 | rotate_x(radians(45))
29 | rotate_z(radians(frame_count / 3.))
30 | fill(255, 200)
31 | stroke(0)
32 | stroke_weight(2)
33 | # draw 3D piramid and get points
34 | base, top, face = prism_3D(sides, p_height, base_radius, top_radius)
35 | pop_matrix()
36 | # draw unfolded 2D
37 | translate(width / 2, height * 3 / 4 - 50)
38 | prism_2D(base, top, face)
39 |
40 |
41 | def prism_3D(np, h, base_r, top_r):
42 | # calculando os points
43 | base_points = []
44 | for i in range(np):
45 | ang = radians(i * 360. / np)
46 | x = sin(ang) * base_r
47 | y = cos(ang) * base_r
48 | base_points.append((x, y, 0))
49 | # edges da base
50 | o_base_points = base_points[1:] + [base_points[0]]
51 | base_edges = list(zip(base_points, o_base_points))
52 | top_points = []
53 | for i in range(np):
54 | ang = radians(i * 360. / np)
55 | x = sin(ang) * top_r
56 | y = cos(ang) * top_r
57 | top_points.append((x, y, h))
58 | # edges da base
59 | o_top_points = top_points[1:] + [top_points[0]]
60 | top_edges = list(zip(top_points, o_top_points))
61 | # edges
62 | for base_edge, top_edge in zip(base_edges, top_edges):
63 | (p1x, p1y, p1z), (p2x, p2y, p2z) = base_edge
64 | (p1tx, p1ty, p1tz), (p2tx, p2ty, p2tz) = top_edge
65 | begin_shape()
66 | vertex(p1x, p1y, p1z)
67 | vertex(p1tx, p1ty, p1tz)
68 | vertex(p2tx, p2ty, p2tz)
69 | vertex(p2x, p2y, p2z)
70 | end_shape(CLOSE)
71 | # one face
72 | (p1x, p1y, p1z), (p2x, p2y, p2z) = base_edges[0]
73 | (p1tx, p1ty, p1tz), (p2tx, p2ty, p2tz) = top_edges[0]
74 | face = [(p2x, p2y, p2z),
75 | (p1x, p1y, p1z),
76 | (p1tx, p1ty, p1tz),
77 | (p2tx, p2ty, p2tz),
78 | ]
79 | # draw base and top
80 | poly_draw(top_points)
81 | poly_draw(base_points)
82 |
83 | return base_points, top_points, face
84 |
85 |
86 | def prism_2D(base, top, face):
87 | with push_matrix():
88 | translate(150, -300)
89 | poly_draw(top, force_z=0)
90 | with push_matrix():
91 | translate(-150, -300)
92 | poly_draw(base, force_z=0)
93 | x0, y0, z0 = face[1]
94 | x2, y2, z2 = face[2]
95 | d = dist(x0, y0, z0, x2, y2, z2)
96 | side = ((150, d - 150), (150, -150))
97 | for i in range(sides):
98 | side = unfold_tri_face(side, face[::-1])
99 | stroke(CUT_STROKE)
100 | line_draw(side[0], side[1])
101 | glue_tab((150, -150), (150, d - 150))
102 |
103 |
104 | def key_pressed():
105 | global base_radius, top_radius, p_height, sides
106 | if key_code == UP:
107 | p_height += 5
108 | if key_code == DOWN:
109 | p_height -= 5
110 | if key_code == LEFT:
111 | base_radius += 5
112 | if key_code == RIGHT:
113 | base_radius -= 5
114 | if key == "w":
115 | sides += 1
116 | if key == "s" and sides > 3:
117 | sides -= 1
118 | if key == "a" and top_radius > 0:
119 | top_radius -= 5
120 | if key == "d":
121 | top_radius += 5
122 | if key == "p":
123 | save_frame(SKETCH_NAME + ".png")
124 |
--------------------------------------------------------------------------------
/unfold_pyramid/unfold_pyramid.py:
--------------------------------------------------------------------------------
1 | """
2 | Alexandre B A Villares - https://abav.lugaralgum.com/sketch-a-day
3 |
4 | - Unfolding pyramids
5 | """
6 |
7 | CUT_STROKE = color(255, 0, 0)
8 | FOLD_STROKE = color(0, 0, 255)
9 |
10 | outer_radius, inner_radius = 100, 50
11 | sides = 5
12 |
13 |
14 | def setup():
15 | size(400, 600, P3D)
16 | hint(ENABLE_DEPTH_TEST)
17 | hint(ENABLE_DEPTH_SORT)
18 |
19 |
20 | def draw():
21 | background(240)
22 | push_matrix()
23 | translate(width / 2, height / 4 + 50)
24 | rotate_x(radians(45))
25 | rotate_z(radians(frame_count / 3.))
26 | fill(255, 200)
27 | stroke(0)
28 | stroke_weight(2)
29 | # draw 3D pyramid and get pts
30 | pts = pyramid_3_d(sides, outer_radius, inner_radius)
31 | pop_matrix()
32 | # draw unfolded 2D
33 | translate(width / 2, height * 3 / 4 - 50)
34 | pyramid_2_d(pts)
35 |
36 |
37 | def pyramid_3_d(np, ext_r, base_r):
38 | # calculando os pts
39 | pts = []
40 | n = np * 2
41 | for i in range(n):
42 | ang = radians(i * 360. / n)
43 | if i % 2 == 0:
44 | r = base_r
45 | else:
46 | r = ext_r
47 | x = sin(ang) * r
48 | y = cos(ang) * r
49 | pts.append((x, y))
50 | # edges da base
51 | base_pts = pts[::2]
52 | o_base_pts = base_pts[1:] + [base_pts[0]]
53 | base_edges = zip(base_pts, o_base_pts)
54 | # calculo da altura
55 | (p0x, p0y), (p1x, p1y) = pts[0], pts[1]
56 | side = dist(p0x, p0y, p1x, p1y)
57 | h_squared = side * side - base_r * base_r
58 | if h_squared > 0: # se a altura viavel
59 | h = sqrt(h_squared)
60 | for edge in base_edges:
61 | p1, p2 = edge
62 | begin_shape()
63 | vertex(*p1)
64 | vertex(*p2)
65 | vertex(0, 0, h)
66 | end_shape(CLOSE)
67 | # always draws base
68 | begin_shape()
69 | for pt in base_pts:
70 | vertex(*pt)
71 | end_shape(CLOSE)
72 | # return pts for 2D!
73 | return pts
74 |
75 |
76 | def pyramid_2_d(pts):
77 | no_fill()
78 | # base fold lines
79 | stroke(FOLD_STROKE)
80 | begin_shape()
81 | for pt in pts[::2]:
82 | vertex(*pt)
83 | end_shape(CLOSE)
84 | # lateral edges
85 | o_pts = pts[1:] + [pts[0]]
86 | edges = zip(pts, o_pts)
87 | for i, edge in enumerate(edges):
88 | p1, p2 = edge
89 | stroke(CUT_STROKE)
90 | if i % 2 == 0:
91 | # abas de cola
92 | glue_tab(p2, p1, 10, )
93 | # FOLD_STROKE
94 | stroke(FOLD_STROKE)
95 | line(p2[0], p2[1], p1[0], p1[1])
96 | else:
97 | # outra edge cortada
98 | line(p1[0], p1[1], p2[0], p2[1])
99 |
100 |
101 | def glue_tab(p1, p2, tab_w, cut_ang=QUARTER_PI / 3):
102 | """
103 | draws a trapezoidal or triangular glue tab along edge defined by p1 and p2,
104 | with width tab_w and cut angle a
105 | """
106 | al = atan2(p1[0] - p2[0], p1[1] - p2[1])
107 | a1 = al + cut_ang + PI
108 | a2 = al - cut_ang
109 | # calculate cut_len to get the base_rght tab width
110 | cut_len = tab_w / sin(cut_ang)
111 | f1 = (p1[0] + cut_len * sin(a1),
112 | p1[1] + cut_len * cos(a1))
113 | f2 = (p2[0] + cut_len * sin(a2),
114 | p2[1] + cut_len * cos(a2))
115 | edge_len = dist(p1[0], p1[1], p2[0], p2[1])
116 |
117 | if edge_len > 2 * cut_len * cos(cut_ang): # 'normal' trapezoidal tab
118 | begin_shape()
119 | vertex(*p1) # vertex(p1[0], p1[1])
120 | vertex(*f1)
121 | vertex(*f2)
122 | vertex(*p2)
123 | end_shape()
124 | else: # short triangular tab
125 | fm = ((f1[0] + f2[0]) / 2, (f1[1] + f2[1]) / 2)
126 | begin_shape()
127 | vertex(*p1)
128 | vertex(*fm) # middle way of f1 and f2
129 | vertex(*p2)
130 | end_shape()
131 |
132 |
133 | def key_pressed():
134 | global inner_radius, outer_radius, sides
135 | if key_code == UP:
136 | outer_radius += 5
137 | if key_code == DOWN:
138 | outer_radius -= 5
139 | if key_code == LEFT:
140 | inner_radius += 5
141 | if key_code == RIGHT:
142 | inner_radius -= 5
143 | if key == "+" or key == "=":
144 | sides += 1
145 | if key == "-" and sides > 3:
146 | sides -= 1
147 |
--------------------------------------------------------------------------------
/box_with_rectangular_holes/frame_box.py:
--------------------------------------------------------------------------------
1 | import py5
2 |
3 | CUT_STROKE, FOLD_STROKE = py5.color(255, 0, 0), py5.color(0, 0, 255)
4 |
5 | def frame_box(w, h, d, thick=0):
6 | """ draw the 3D version of the box with rectangular holes """
7 | mw, mh, md = w / 2., h / 2., d / 2.
8 | py5.translate(0, 0, -md) # base
9 | face(0, 0, w, h, thick)
10 | py5.translate(0, 0, d) # top
11 | face(0, 0, w, h, thick)
12 | py5.translate(0, 0, -md) # back to 0
13 | py5.rotate_y(py5.HALF_PI)
14 | py5.translate(0, 0, -mw) # left side
15 | face(0, 0, d, h, thick)
16 | py5.translate(0, 0, w) # right side
17 | face(0, 0, d, h, thick)
18 | py5.translate(0, 0, -mw) # back to middle
19 | py5.rotate_y(-py5.HALF_PI) # back to 0 rotation
20 | py5.rotate_x(py5.HALF_PI)
21 | py5.translate(0, 0, -mh) # lateral e
22 | face(0, 0, w, d, thick)
23 | py5.translate(0, 0, h) # lateral d
24 | face(0, 0, w, d, thick)
25 | py5.translate(0, 0, -mw) # reset translate
26 | py5.rotate_x(-py5.HALF_PI) # reset rotate
27 |
28 |
29 | def face(x, y, w, h, thick):
30 | mw, mh = w / 2., h / 2.
31 | py5.push_matrix()
32 | py5.translate(x, y)
33 | py5.begin_shape()
34 | py5.vertex(-mw, -mh)
35 | py5.vertex(+mw, -mh)
36 | py5.vertex(+mw, +mh)
37 | py5.vertex(-mw, +mh)
38 | if thick > 0 and mw - thick > 0 and mh - thick > 0:
39 | mw -= thick
40 | mh -= thick
41 | py5.begin_contour() # counterclockwise hole
42 | py5.vertex(-mw, -mh)
43 | py5.vertex(-mw, +mh)
44 | py5.vertex(+mw, +mh)
45 | py5.vertex(+mw, -mh)
46 | py5.end_contour()
47 | py5.end_shape(py5.CLOSE)
48 | py5.pop_matrix()
49 |
50 |
51 | def unfolded_frame_box(w, h, d, thick=0, draw_main=True):
52 | mw, mh, md = w / 2., h / 2., d / 2.
53 | unfolded_face(0, -h - md, w, d, "aaan", thick, draw_main)
54 | unfolded_face(0, -mh, w, h, "vvvv", thick, draw_main)
55 | unfolded_face(0, -mh + mh + md, w, d, "cncv", thick, draw_main)
56 | unfolded_face(0, +mh + d, w, h, "cncc", thick, draw_main)
57 | unfolded_face(-mw - md, -mh, d, h, "acna", thick, draw_main)
58 | unfolded_face(mw + md, -mh, d, h, "ncaa", thick, draw_main)
59 |
60 |
61 | def unfolded_face(x, y, w, h, edge_types, thick=0, draw_main=True):
62 | e0, e1, e2, e3 = edge_types
63 | mw, mh = w / 2., h / 2.
64 | py5.push_matrix()
65 | py5.translate(x, y)
66 | if draw_main:
67 | edge(-mw, +mh, -mw, -mh, e0)
68 | edge(-mw, -mh, +mw, -mh, e1)
69 | edge(+mw, -mh, +mw, +mh, e2)
70 | edge(+mw, +mh, -mw, +mh, e3)
71 | if thick > 0 and mw - thick > 0 and mh - thick > 0:
72 | unfolded_face(0, 0, w - thick * 2, h - thick * 2, "cccc")
73 | py5.pop_matrix()
74 |
75 |
76 | def edge(x0, y0, x1, y1, edge_type):
77 | if edge_type == "n": # no edge is drawn
78 | return
79 | elif edge_type == "c": # cut stroke selected
80 | py5.stroke(CUT_STROKE)
81 | else:
82 | py5.stroke(FOLD_STROKE) # fold stroke selected for "v" and "a"
83 | py5.line(x0, y0, x1, y1) # line drawn here
84 | if edge_type == "a": # tab (note a fold-stroke line was already drawn)
85 | py5.stroke(CUT_STROKE)
86 | py5.no_fill()
87 | glue_tab((x0, y0), (x1, y1), 10)
88 |
89 |
90 | def glue_tab(p1, p2, tab_w, cut_ang=py5.QUARTER_PI / 3):
91 | """
92 | draws a trapezoidal or triangular glue tab along edge defined by p1 and p2,
93 | with width tab_w and cut angle a
94 | """
95 | al = py5.atan2(p1[0] - p2[0], p1[1] - p2[1])
96 | a1 = al + cut_ang + py5.PI
97 | a2 = al - cut_ang
98 | # calculate cut_len to get the right tab width
99 | cut_len = tab_w / py5.sin(cut_ang)
100 | f1 = (p1[0] + cut_len * py5.sin(a1),
101 | p1[1] + cut_len * py5.cos(a1))
102 | f2 = (p2[0] + cut_len * py5.sin(a2),
103 | p2[1] + cut_len * py5.cos(a2))
104 | edge_len = py5.dist(p1[0], p1[1], p2[0], p2[1])
105 |
106 | if edge_len > 2 * cut_len * py5.cos(cut_ang): # 'normal' trapezoidal tab
107 | py5.begin_shape()
108 | py5.vertex(*p1) # vertex(p1[0], p1[1])
109 | py5.vertex(*f1)
110 | py5.vertex(*f2)
111 | py5.vertex(*p2)
112 | py5.end_shape()
113 | else: # short triangular tab
114 | fm = ((f1[0] + f2[0]) / 2, (f1[1] + f2[1]) / 2)
115 | py5.begin_shape()
116 | py5.vertex(*p1)
117 | py5.vertex(*fm) # middle way of f1 and f2
118 | py5.vertex(*p2)
119 | py5.end_shape()
120 |
--------------------------------------------------------------------------------
/box_with_circular_holes/frame_box.py:
--------------------------------------------------------------------------------
1 | import py5
2 |
3 | CUT_STROKE, FOLD_STROKE = py5.color(255, 0, 0), py5.color(0, 0, 255)
4 |
5 | def frame_box(w, h, d, thick=0):
6 | """ draw the 3D version of the box with rectangular holes """
7 | mw, mh, md = w / 2., h / 2., d / 2.
8 | py5.translate(0, 0, -md) # base
9 | face(0, 0, w, h, thick)
10 | py5.translate(0, 0, d) # top
11 | face(0, 0, w, h, thick)
12 | py5.translate(0, 0, -md) # back to 0
13 | py5.rotate_y(py5.HALF_PI)
14 | py5.translate(0, 0, -mw) # left side
15 | face(0, 0, d, h, thick)
16 | py5.translate(0, 0, w) # right side
17 | face(0, 0, d, h, thick)
18 | py5.translate(0, 0, -mw) # back to middle
19 | py5.rotate_y(-py5.HALF_PI) # back to 0 rotation
20 | py5.rotate_x(py5.HALF_PI)
21 | py5.translate(0, 0, -mh) # lateral e
22 | face(0, 0, w, d, thick)
23 | py5.translate(0, 0, h) # lateral d
24 | face(0, 0, w, d, thick)
25 | py5.translate(0, 0, -mw) # reset translate
26 | py5.rotate_x(-py5.HALF_PI) # reset rotate
27 |
28 |
29 | def face(x, y, w, h, e):
30 | mw, mh = w / 2., h / 2.
31 | py5.push_matrix()
32 | py5.translate(x, y)
33 | py5.begin_shape()
34 | py5.vertex(-mw, -mh)
35 | py5.vertex(+mw, -mh)
36 | py5.vertex(+mw, +mh)
37 | py5.vertex(-mw, +mh)
38 | hole(mw, mh, e)
39 | py5.end_shape(py5.CLOSE)
40 | py5.pop_matrix()
41 |
42 |
43 | def hole(mw, mh, e):
44 | if e > 0 and mw - e > 0 and mh - e > 0:
45 | py5.begin_contour()
46 | np = 24
47 | for i in range(np):
48 | ang = py5.TWO_PI / np * i
49 | x = py5.sin(ang) * e
50 | y = py5.cos(ang) * e
51 | py5.vertex(x, y)
52 | py5.end_contour()
53 |
54 |
55 | def unfolded_frame_box(w, h, d, thick=0, draw_main=True):
56 | mw, mh, md = w / 2., h / 2., d / 2.
57 | unfolded_face(0, -h - md, w, d, "aaan", thick, draw_main)
58 | unfolded_face(0, -mh, w, h, "vvvv", thick, draw_main)
59 | unfolded_face(0, -mh + mh + md, w, d, "cncv", thick, draw_main)
60 | unfolded_face(0, +mh + d, w, h, "cncc", thick, draw_main)
61 | unfolded_face(-mw - md, -mh, d, h, "acna", thick, draw_main)
62 | unfolded_face(mw + md, -mh, d, h, "ncaa", thick, draw_main)
63 |
64 |
65 | def unfolded_face(x, y, w, h, edge_types, thick=0, draw_main=True):
66 | e0, e1, e2, e3 = edge_types
67 | mw, mh = w / 2., h / 2.
68 | py5.push_matrix()
69 | py5.translate(x, y)
70 | if draw_main:
71 | edge(-mw, +mh, -mw, -mh, e0)
72 | edge(-mw, -mh, +mw, -mh, e1)
73 | edge(+mw, -mh, +mw, +mh, e2)
74 | edge(+mw, +mh, -mw, +mh, e3)
75 | if thick > 0 and mw - thick > 0 and mh - thick > 0:
76 | py5.stroke(CUT_STROKE)
77 | py5.circle(0, 0, thick * 2)
78 | py5.pop_matrix()
79 |
80 |
81 | def edge(x0, y0, x1, y1, edge_type):
82 | if edge_type == "n": # no edge is drawn
83 | return
84 | elif edge_type == "c": # cut stroke selected
85 | py5.stroke(CUT_STROKE)
86 | else:
87 | py5.stroke(FOLD_STROKE) # fold stroke selected for "v" and "a"
88 | py5.line(x0, y0, x1, y1) # line drawn here
89 | if edge_type == "a": # tab (note a fold-stroke line was already drawn)
90 | py5.stroke(CUT_STROKE)
91 | py5.no_fill()
92 | glue_tab((x0, y0), (x1, y1), 10)
93 |
94 |
95 | def glue_tab(p1, p2, tab_w, cut_ang=py5.QUARTER_PI / 3):
96 | """
97 | draws a trapezoidal or triangular glue tab along edge defined by p1 and p2,
98 | with width tab_w and cut angle a
99 | """
100 | al = py5.atan2(p1[0] - p2[0], p1[1] - p2[1])
101 | a1 = al + cut_ang + py5.PI
102 | a2 = al - cut_ang
103 | # calculate cut_len to get the right tab width
104 | cut_len = tab_w / py5.sin(cut_ang)
105 | f1 = (p1[0] + cut_len * py5.sin(a1),
106 | p1[1] + cut_len * py5.cos(a1))
107 | f2 = (p2[0] + cut_len * py5.sin(a2),
108 | p2[1] + cut_len * py5.cos(a2))
109 | edge_len = py5.dist(p1[0], p1[1], p2[0], p2[1])
110 |
111 | if edge_len > 2 * cut_len * py5.cos(cut_ang): # 'normal' trapezoidal tab
112 | py5.begin_shape()
113 | py5.vertex(*p1) # vertex(p1[0], p1[1])
114 | py5.vertex(*f1)
115 | py5.vertex(*f2)
116 | py5.vertex(*p2)
117 | py5.end_shape()
118 | else: # short triangular tab
119 | fm = ((f1[0] + f2[0]) / 2, (f1[1] + f2[1]) / 2)
120 | py5.begin_shape()
121 | py5.vertex(*p1)
122 | py5.vertex(*fm) # middle way of f1 and f2
123 | py5.vertex(*p2)
124 | py5.end_shape()
125 |
126 |
127 |
--------------------------------------------------------------------------------
/unfold_pyramidal_solid_py5/geometry.py:
--------------------------------------------------------------------------------
1 | import py5
2 |
3 | CUT_STROKE = py5.color(255, 0, 0)
4 |
5 |
6 | def unfold_tri_face(pts_2D, pts_3D):
7 | """
8 | gets a collection of 2 (B, C) starting 2D points (Py5Vectors or tuples)
9 | gets a collection of 4 (A, B, C, D) 3D points (p_vectors or tuples)
10 | draws the unfolded face and returns (A, D) 2D positions.
11 | """
12 | b2D, c2D = pts_2D
13 | a3D, b3D, c3D, d3D = pts_3D
14 | bd_len = py5.dist(b3D[0], b3D[1], b3D[2], d3D[0], d3D[1], d3D[2])
15 | cd_len = py5.dist(c3D[0], c3D[1], c3D[2], d3D[0], d3D[1], d3D[2])
16 | # lower triangle
17 | d2D = third_point(b2D, c2D, bd_len, cd_len)[
18 | 0] # gets the first solution
19 | line_draw(b2D, c2D)
20 | line_draw(d2D, c2D, tab=True)
21 | # upper triangle (fixed from 190408a)
22 | ab_len = py5.dist(b3D[0], b3D[1], b3D[2], a3D[0], a3D[1], a3D[2])
23 | ad_len = py5.dist(a3D[0], a3D[1], a3D[2], d3D[0], d3D[1], d3D[2])
24 | # gets the 1st solution too!
25 | a2D = third_point(b2D, d2D, ab_len, ad_len)[0]
26 | line_draw(b2D, a2D, tab=True)
27 | # line_draw(d2D, a2D)
28 | return (a2D, d2D)
29 |
30 |
31 | def third_point(a, b, ac_len, bc_len):
32 | """
33 | Adapted from code by monkut https://stackoverflow.com/users/24718/monkut
34 | at https://stackoverflow.com/questions/4001948/drawing-a-triangle-in-a-coordinate-plane-given-its-three-sides
35 | for use with processing python mode - using p_vectors
36 |
37 | returns two point c options given:
38 | point a, point b, ac length, bc length
39 | """
40 | class NoTrianglePossible(BaseException):
41 | pass
42 |
43 | # To allow use of tuples, creates or recreates PVectors
44 | a, b = py5.Py5Vector(*a), py5.Py5Vector(*b)
45 | # check if a triangle is possible
46 | ab_len = a.dist(b)
47 | if ab_len > (ac_len + bc_len) or ab_len < abs(ac_len - bc_len):
48 | raise no_triangle_possible("The sides do not form a triangle")
49 |
50 | # get the length to the vertex of the right triangle formed,
51 | # by the intersection formed by circles a and b
52 | ad_len = (ab_len ** 2 + ac_len ** 2 - bc_len ** 2) / (2.0 * ab_len)
53 | # get the height of the line at a right angle from a_len
54 | h = py5.sqrt(abs(ac_len ** 2 - ad_len ** 2))
55 |
56 | # Calculate the mid point d, needed to calculate point c(1|2)
57 | d = py5.Py5Vector(a.x + ad_len * (b.x - a.x) / ab_len,
58 | a.y + ad_len * (b.y - a.y) / ab_len)
59 | # get point c locations
60 | c1 = py5.Py5Vector(d.x + h * (b.y - a.y) / ab_len,
61 | d.y - h * (b.x - a.x) / ab_len)
62 | c2 = py5.Py5Vector(d.y + h * (b.x - a.x) / ab_len,
63 | d.x - h * (b.y - a.y) / ab_len)
64 | return c1, c2
65 |
66 |
67 | def line_draw(p1, p2, tab=False):
68 | """
69 | sugar for drawing lines from 2 "points" (tuples or p_vectors)
70 | may also draw a glue tab suitably marked for cutting.
71 | """
72 | py5.line(p1[0], p1[1], p2[0], p2[1])
73 | if tab:
74 | with py5.push_style():
75 | py5.stroke(CUT_STROKE)
76 | glue_tab(p1, p2)
77 |
78 |
79 | def glue_tab(p1, p2, tab_w=10, cut_ang=py5.QUARTER_PI):
80 | """
81 | draws a trapezoidal or triangular glue tab
82 | along edge defined by p1 and p2, with provided
83 | width (tab_w) and cut angle (cut_ang)
84 | """
85 | a1 = py5.atan2(p1[0] - p2[0], p1[1] - p2[1]) + cut_ang + py5.PI
86 | a2 = py5.atan2(p1[0] - p2[0], p1[1] - p2[1]) - cut_ang
87 | # calculate cut_len to get the right tab width
88 | cut_len = tab_w / py5.sin(cut_ang)
89 | f1 = (p1[0] + cut_len * py5.sin(a1),
90 | p1[1] + cut_len * py5.cos(a1))
91 | f2 = (p2[0] + cut_len * py5.sin(a2),
92 | p2[1] + cut_len * py5.cos(a2))
93 | edge_len = py5.dist(p1[0], p1[1], p2[0], p2[1])
94 |
95 | if edge_len > 2 * cut_len * py5.cos(cut_ang): # 'normal' trapezoidal tab
96 | line_draw(p1, f1)
97 | line_draw(f1, f2)
98 | line_draw(f2, p2)
99 | else: # short triangular tab
100 | fm = ((f1[0] + f2[0]) / 2, (f1[1] + f2[1]) / 2)
101 | line_draw(p1, fm)
102 | line_draw(fm, p2)
103 |
104 | DEBUG = True
105 |
106 | def debug_text(name, pts, enum=False):
107 | if DEBUG:
108 | for i, p in enumerate(pts):
109 | with py5.push():
110 |
111 | py5.fill(255, 0, 0)
112 | if enum:
113 | py5.translate(0, -5, 10)
114 | py5.text(name + "-" + str(i), *p)
115 | else:
116 | py5.translate(10, 10, 10)
117 | py5.text(name[i], *p)
118 |
119 |
120 | def poly_draw(pts, force_z=None, closed=True):
121 | """ sugar for face drawing """
122 | py5.begin_shape()
123 | for p in pts:
124 | if force_z is None:
125 | py5.vertex(*p)
126 | else:
127 | py5.vertex(p[0], p[1], force_z)
128 | if closed:
129 | py5.end_shape(py5.CLOSE)
130 | else:
131 | py5.end_shape()
132 |
133 |
134 | def triangulated_face(*args):
135 | if len(args) == 4:
136 | a, b, c, d = args
137 | print("face")
138 | else:
139 | a, b, c, d = args[0]
140 | # two triangles - could be with a diferent diagonal!
141 | # TODO: let one choose diagonal orientation
142 | py5.stroke(0)
143 | poly_draw((a, b, d))
144 | poly_draw((b, d, c))
145 |
146 |
147 | def test():
148 | #size(600, 400, P3D)
149 | p3D = [(50, 100, 0), (200, 100, 0), (200, 200, 0), (100, 300, -100)]
150 | debug_text("ABCD", p3D)
151 | py5.begin_shape()
152 | for p in p3D:
153 | py5.vertex(*p)
154 | py5.end_shape(py5.CLOSE)
155 | x0, y0, z0 = p3D[1]
156 | x2, y2, z2 = p3D[3]
157 | py5.line(x0, y0, z0, x2, y2, z2)
158 | print(py5.dist(x0, y0, z0, x2, y2, z2))
159 |
160 | p2D = [(250, 100), (250, 200)]
161 | bx, by = p2D[0]
162 | debug_text("BC", p2D)
163 | for i in range(1):
164 | p2D = unfold_tri_face(p2D, p3D)
165 | print(p2D)
166 | debug_text("AD", p2D)
167 | dx, dy, _ = p2D[1]
168 | print(py5.dist(bx, by, dx, dy))
169 |
170 |
--------------------------------------------------------------------------------
/paraboloid_box_v0/draw_2D.py:
--------------------------------------------------------------------------------
1 | import py5
2 |
3 | from draw_3D import poly_draw
4 | from debug import debug_text
5 |
6 | CUT_COLOR = py5.color(255, 0, 0) # Color to mark outline cut
7 | ENG_COLOR = py5.color(0, 0, 255) # Color to mark folding/engraving
8 | TAB_W = 10 # glue tab width
9 | TAB_A = py5.radians(30) # glue tab angle
10 |
11 |
12 | def draw_unfolded(box_w, box_d, ab_l, cd_l, face_data):
13 | """
14 | main 2D drawing procedure
15 | takes 2 box dimentions, 2 top point height lists,
16 | and a collection of 3D points (face_data) from the 3D procedure
17 | then draws the unfolded version of the volume with glue tabs
18 | """
19 | ah, bh, ch, dh = ab_l[0], ab_l[-1], cd_l[0], cd_l[-1]
20 | ah_2d, a0_2d = (box_w * 2 + box_d, -ah), (box_w * 2 + box_d, 0)
21 | bh_2d, b0_2d = (0, -bh), (0, 0)
22 | ch_2d, c0_2d = (box_w, -ch), (box_w, 0)
23 | dh_2d, d0_2d = (box_w + box_d, -dh), (box_w + box_d, 0)
24 |
25 | py5.no_fill()
26 | # Marked for folding
27 | py5.stroke(ENG_COLOR)
28 | # verticals
29 | line_draw(b0_2d, bh_2d)
30 | line_draw(c0_2d, ch_2d)
31 | line_draw(d0_2d, dh_2d)
32 | line_draw(a0_2d, ah_2d)
33 | debug_text("BCDA", (bh_2d, ch_2d, dh_2d, ah_2d))
34 |
35 | # divided top face - also draws some CUT_COLOR glue tabs!
36 | start_1, start_2 = bh_2d, ch_2d
37 | for a_3d, b_3d, c_3d, d_3d in face_data:
38 | start_1, start_2 = unfold_tri_face((start_1, start_2),
39 | (a_3d, b_3d, c_3d, d_3d))
40 |
41 | # top tab
42 | line_draw(start_1, start_2, tab=True)
43 | # floor face
44 | py5.rect(0, 0, box_w, box_d)
45 |
46 | # Marked for cutting
47 | py5.stroke(CUT_COLOR)
48 | # middle tab
49 | glue_tab(b0_2d, bh_2d, TAB_W, TAB_A)
50 | # floor tabs
51 | glue_tab((0, box_d), b0_2d, TAB_W, TAB_A)
52 | glue_tab((box_w, box_d), (0, box_d), TAB_W, TAB_A)
53 | glue_tab((box_w, 0), (box_w, box_d), TAB_W, TAB_A)
54 | # main outline cut
55 | num_pts = len(cd_l)
56 | cd_2_dpts = [(box_w + box_d * i / (num_pts - 1), -cd_l[i])
57 | for i in range(num_pts)]
58 | ab_2_dpts = [(box_w * 2 + box_d + box_d * i / (num_pts - 1), -ab_l[i])
59 | for i in range(num_pts)]
60 | main_outline = cd_2_dpts + ab_2_dpts + [(box_w * 2 + box_d * 2, 0), c0_2d]
61 | poly_draw(main_outline, closed=False)
62 |
63 |
64 | def line_draw(p1, p2, tab=False):
65 | """
66 | sugar for drawing lines from 2 "points" (tuples or PVectors)
67 | may also draw a glue tab suitably marked for cutting.
68 | """
69 | py5.line(p1[0], p1[1], p2[0], p2[1])
70 | if tab:
71 | with py5.push_style():
72 | py5.stroke(CUT_COLOR)
73 | glue_tab(p1, p2, TAB_W, TAB_A)
74 |
75 |
76 | def glue_tab(p1, p2, tab_w=10, cut_ang=py5.QUARTER_PI):
77 | """
78 | draws a trapezoidal or triangular glue tab
79 | along edge defined by p1 and p2, with provided
80 | width (tab_w) and cut angle (cut_ang)
81 | """
82 | a1 = py5.atan2(p1[0] - p2[0], p1[1] - p2[1]) + cut_ang + py5.PI
83 | a2 = py5.atan2(p1[0] - p2[0], p1[1] - p2[1]) - cut_ang
84 | # calculate cut_len to get the right tab width
85 | cut_len = tab_w / py5.sin(cut_ang)
86 | f1 = py5.Py5Vector(p1[0] + cut_len * py5.sin(a1),
87 | p1[1] + cut_len * py5.cos(a1))
88 | f2 = py5.Py5Vector(p2[0] + cut_len * py5.sin(a2),
89 | p2[1] + cut_len * py5.cos(a2))
90 | edge_len = py5.dist(p1[0], p1[1], p2[0], p2[1])
91 |
92 | if edge_len > 2 * cut_len * py5.cos(cut_ang): # 'normal' trapezoidal tab
93 | line_draw(p1, f1)
94 | line_draw(f1, f2)
95 | line_draw(f2, p2)
96 | else: # short triangular tab
97 | fm = ((f1[0] + f2[0]) / 2, (f1[1] + f2[1]) / 2)
98 | line_draw(p1, fm)
99 | line_draw(fm, p2)
100 |
101 |
102 | def unfold_tri_face(pts_2_d, pts_3_d):
103 | """
104 | gets a collection of 2 (B, D) starting 2D points (PVectors or tuples)
105 | Gets a collection of 4 (A, B, C, D) 3D points (PVectors or tuples)
106 | Draws the unfolded face a returns (A, C) 2D positions.
107 | """
108 | b2_d, c2_d = pts_2_d
109 | a3_d, b3_d, c3_d, d3_d = pts_3_d
110 | bd_len = py5.dist(b3_d[0], b3_d[1], b3_d[2], d3_d[0], d3_d[1], d3_d[2])
111 | cd_len = py5.dist(c3_d[0], c3_d[1], c3_d[2], d3_d[0], d3_d[1], d3_d[2])
112 | # lower triangle
113 | d2_d = third_point(b2_d, c2_d, bd_len, cd_len)[
114 | 0] # gets the first solution
115 | line_draw(b2_d, c2_d)
116 | line_draw(b2_d, d2_d)
117 | line_draw(d2_d, c2_d, tab=True)
118 | # upper triangle (fixed from 190408a)
119 | ab_len = py5.dist(b3_d[0], b3_d[1], b3_d[2], a3_d[0], a3_d[1], a3_d[2])
120 | ad_len = py5.dist(a3_d[0], a3_d[1], a3_d[2], d3_d[0], d3_d[1], d3_d[2])
121 | # gets the 1st solution too!
122 | a2_d = third_point(b2_d, d2_d, ab_len, ad_len)[0]
123 | line_draw(b2_d, a2_d, tab=True)
124 | line_draw(d2_d, a2_d)
125 | return (a2_d, d2_d)
126 |
127 |
128 | def third_point(a, b, ac_len, bc_len):
129 | """
130 | Adapted from code by Monkut https://stackoverflow.com/users/24718/monkut
131 | at https://stackoverflow.com/questions/4001948/drawing-a-triangle-in-a-coordinate-plane-given-its-three-sides
132 |
133 | Returns two point c options given:
134 | point a, point b, ac length, bc length
135 | """
136 | class NoTrianglePossible(BaseException):
137 | pass
138 |
139 | # To allow use of tuples, creates or recreates PVectors
140 | a, b = py5.Py5Vector(*a), py5.Py5Vector(*b)
141 | # check if a triangle is possible
142 | ab_len = a.dist(b)
143 | if ab_len > (ac_len + bc_len) or ab_len < abs(ac_len - bc_len):
144 | raise NoTrianglePossible("The sides do not form a triangle")
145 |
146 | # get the length to the vertex of the right triangle formed,
147 | # by the intersection formed by circles a and b
148 | ad_len = (ab_len ** 2 + ac_len ** 2 - bc_len ** 2) / (2.0 * ab_len)
149 | # get the height of the line at a right angle from a_len
150 | h = py5.sqrt(abs(ac_len ** 2 - ad_len ** 2))
151 |
152 | # Calculate the mid point d, needed to calculate point c(1|2)
153 | d = py5.Py5Vector(a.x + ad_len * (b.x - a.x) / ab_len,
154 | a.y + ad_len * (b.y - a.y) / ab_len)
155 | # get point c locations
156 | c1 = py5.Py5Vector(d.x + h * (b.y - a.y) / ab_len,
157 | d.y - h * (b.x - a.x) / ab_len)
158 | c2 = py5.Py5Vector(d.y + h * (b.x - a.x) / ab_len,
159 | d.x - h * (b.y - a.y) / ab_len)
160 | return c1, c2
161 |
162 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2023 Alexandre B A Villares
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------