|
1 | 1 | # ##### BEGIN GPL LICENSE BLOCK #####
|
2 | 2 | #
|
3 | 3 | # Basket Arch, a Blender addon
|
4 |
| -# (c) 2014,2015,2020 Michel J. Anders (varkenvarken) |
| 4 | +# (c) 2014,2015,2020,2025 Michel J. Anders (varkenvarken) |
5 | 5 | #
|
6 | 6 | # This program is free software; you can redistribute it and/or
|
7 | 7 | # modify it under the terms of the GNU General Public License
|
|
19 | 19 | #
|
20 | 20 | # ##### END GPL LICENSE BLOCK #####
|
21 | 21 |
|
| 22 | +# This add-on creates a basket arch (a.k.a. three centered arch) mesh, |
| 23 | +# which is a type of arch with a specific geometry, often used in architecture. |
| 24 | +# See# https://en.wikipedia.org/wiki/Basket_arch for more information. |
| 25 | + |
22 | 26 | bl_info = {
|
23 |
| - "name": "Basket Arch", |
24 |
| - "author": "Michel Anders (varkenvarken)", |
25 |
| - "version": (0, 0, 202005010941), |
26 |
| - "blender": (2, 83, 0), |
27 |
| - "location": "View3D > Add > Mesh", |
28 |
| - "description": "Adds a basket arch mesh", |
29 |
| - "warning": "", |
30 |
| - "wiki_url": "", |
31 |
| - "tracker_url": "", |
32 |
| - "category": "Add Mesh"} |
33 |
| - |
34 |
| -from math import atan2, asin, sin, cos, pi, degrees, sqrt |
| 27 | + "name": "Basket Arch", |
| 28 | + "author": "Michel Anders (varkenvarken)", |
| 29 | + "version": (0, 0, 20250608095121), |
| 30 | + "blender": (2, 83, 0), # verified to work with Blender 4.4 |
| 31 | + "location": "View3D > Add > Mesh", |
| 32 | + "description": "Adds a basket arch mesh", |
| 33 | + "warning": "", |
| 34 | + "wiki_url": "https://github.com/varkenvarken/blenderaddons", |
| 35 | + "tracker_url": "", |
| 36 | + "category": "Add Mesh", |
| 37 | +} |
| 38 | + |
| 39 | +from math import atan2, sin, cos, pi, sqrt |
35 | 40 | import bpy, bmesh
|
36 |
| -from bpy.props import FloatProperty, BoolProperty, FloatVectorProperty, IntProperty, BoolVectorProperty, StringProperty |
| 41 | +from bpy.props import FloatProperty, IntProperty |
37 | 42 | from bpy_extras import object_utils
|
38 | 43 |
|
39 |
| -def circle(x,y,r,s,e,w): |
40 |
| - co = [] |
41 |
| - # in Blender z is considered up |
42 |
| - if s <= e : |
43 |
| - while s <= e : |
44 |
| - co.append((x+r*cos(s),0,y+r*sin(s))) |
45 |
| - s += w |
46 |
| - else: |
47 |
| - while s >= e : |
48 |
| - co.append((x+r*cos(s),0,y+r*sin(s))) |
49 |
| - s -= w |
50 |
| - return co |
51 |
| - |
52 |
| -def calc_radii(W,H): |
53 |
| - r = 2*H/3.0 |
54 |
| - h = H/3.0 |
55 |
| - w = W/2.0 |
56 |
| - alpha = atan2(h, w) |
57 |
| - b = sqrt(w*w + h*h) |
58 |
| - s = b/sin(alpha) |
59 |
| - c = s - h |
60 |
| - R = c + H |
61 |
| - |
62 |
| - return r,R,c |
63 |
| - |
64 |
| -def R(w,h): |
65 |
| - """ |
66 |
| - calculate the y poistion of the center circle of a basket arch given half the width and the height. |
67 |
| - """ |
68 |
| - a = h/3.0 |
69 |
| - beta = atan2(a,w) |
70 |
| - c = sqrt(a**2 + w**2) |
71 |
| - d = c/2.0 |
72 |
| - e = d/sin(beta) |
73 |
| - return e-a |
74 |
| - |
75 |
| -def c(xA,yA,rA, xB,yB,rB): |
76 |
| - """ |
77 |
| - return the interection point(s) of two circles. |
78 |
| - """ |
79 |
| - d2 = (xB-xA)**2 + (yB-yA)**2 |
80 |
| - K = (1/4.0) * sqrt(((rA+rB)**2-d2) * (d2-(rA-rB)**2)) |
81 |
| - |
82 |
| - xb = (1/2.0)*(xB+xA) + (1/2.0)*(xB-xA)*(rA**2-rB**2)/d2 |
83 |
| - dx = 2*(yB-yA)*K/d2 |
84 |
| - yb = (1/2.0)*(yB+yA) + (1/2.0)*(yB-yA)*(rA**2-rB**2)/d2 |
85 |
| - dy = -2*(xB-xA)*K/d2 |
86 |
| - |
87 |
| - return ((xb+dx,yb+dy), (xb-dx,yb-dy)) |
88 |
| - |
89 |
| - |
90 |
| -def basket_arch(W,D,H=0,resolution=1): |
91 |
| - if (H > 0) and (W/H >= 2.0) and (W/H <= 4.0): |
92 |
| - w=2*H/3.0 |
93 |
| - s=W/2.0-w |
94 |
| - rc = R(s,H) |
95 |
| - r = rc + H |
96 |
| - c1x = -s |
97 |
| - c1y = 0 |
98 |
| - cmx = 0 |
99 |
| - cmy = -rc |
100 |
| - c2x = s |
101 |
| - c2y = 0 |
102 |
| - CX,CY = c(c1x,c2x,w, cmx,cmy,r)[0] |
103 |
| - dx = -CX |
104 |
| - dy = CY + rc |
105 |
| - beta = atan2(dx,dy) |
106 |
| - dx = -CX-s |
107 |
| - dy = CY |
108 |
| - alpha = atan2(dx,dy) |
109 |
| - |
110 |
| - else: |
111 |
| - w = W / 4.0 |
112 |
| - h = sqrt(3.0) * w |
113 |
| - r = 3.0 * w |
114 |
| - c1x = -w |
115 |
| - c1y = 0 |
116 |
| - cmx = 0 |
117 |
| - cmy = -h |
118 |
| - c2x = w |
119 |
| - c2y = 0 |
120 |
| - #alpha = atan2(h, 2.0 * w) |
121 |
| - beta = atan2(w , h) |
122 |
| - alpha = beta |
123 |
| - |
124 |
| - co = circle(c1x, c1y, w, pi , pi / 2.0 + alpha, resolution * 3*pi/360) |
125 |
| - co += circle(cmx, cmy, r, pi / 2.0 + beta, pi / 2.0 - beta, resolution * pi/360) |
126 |
| - # reversing the generation of the points in this segment will ensure these segments are symmetrical |
127 |
| - # reversing the resulting list is necessary to maintain the order of the generated polygons |
128 |
| - co += reversed(circle(c2x, c2y, w, 0.0 , pi / 2.0 - alpha, resolution * 3*pi/360)) |
129 |
| - |
130 |
| - n = len(co) |
131 |
| - return co + [(x,y+D,z) for x,y,z in co], [(i,i+1,n+i+1,n+i) for i in range(n-1)] |
132 |
| - |
133 |
| -class BasketArch(bpy.types.Operator,object_utils.AddObjectHelper): |
134 |
| - bl_idname = 'mesh.basketarch' |
135 |
| - bl_label = 'Create a basket arch' |
136 |
| - bl_options = {'REGISTER','UNDO'} |
137 |
| - |
138 |
| - width : FloatProperty( |
139 |
| - name="Width",description="Width of the basket arch (span)", |
140 |
| - default=4, |
141 |
| - subtype='DISTANCE', |
142 |
| - unit='LENGTH') |
143 |
| - height : FloatProperty( |
144 |
| - name="Height",description="Height of the basket arch (0 if classical)", |
145 |
| - default=0, |
146 |
| - subtype='DISTANCE', |
147 |
| - unit='LENGTH') |
148 |
| - depth : FloatProperty( |
149 |
| - name="Depth",description="Depth of the basket arch", |
150 |
| - default=1, |
151 |
| - subtype='DISTANCE', |
152 |
| - unit='LENGTH') |
153 |
| - resolution : IntProperty( |
154 |
| - name="Resolution", description="Higher values = less polygons", |
155 |
| - default=1, |
156 |
| - min = 1) |
157 |
| - |
158 |
| - def execute(self, context): |
159 |
| - |
160 |
| - verts_loc, faces = basket_arch(self.width, self.depth, self.height, self.resolution) |
161 |
| - |
162 |
| - mesh = bpy.data.meshes.new("BasketArch") |
163 |
| - |
164 |
| - bm = bmesh.new() |
165 |
| - |
166 |
| - for v_co in verts_loc: |
167 |
| - bm.verts.new(v_co) |
168 |
| - |
169 |
| - # see http://blenderartists.org/forum/archive/index.php/t-354412.html for next bit |
170 |
| - if hasattr(bm.verts, "ensure_lookup_table"): |
171 |
| - bm.verts.ensure_lookup_table() |
172 |
| - |
173 |
| - for f_idx in faces: |
174 |
| - bm.faces.new([bm.verts[i] for i in f_idx]) |
175 |
| - |
176 |
| - bm.to_mesh(mesh) |
177 |
| - mesh.update() |
178 |
| - |
179 |
| - object_utils.object_data_add(context, mesh, operator=self) |
180 |
| - |
181 |
| - return {"FINISHED"} |
| 44 | + |
| 45 | +def circle(x, y, r, s, e, w): |
| 46 | + co = [] |
| 47 | + # in Blender z is considered up |
| 48 | + if s <= e: |
| 49 | + while s <= e: |
| 50 | + co.append((x + r * cos(s), 0, y + r * sin(s))) |
| 51 | + s += w |
| 52 | + else: |
| 53 | + while s >= e: |
| 54 | + co.append((x + r * cos(s), 0, y + r * sin(s))) |
| 55 | + s -= w |
| 56 | + return co |
| 57 | + |
| 58 | + |
| 59 | +def calc_radii(W, H): |
| 60 | + r = 2 * H / 3.0 |
| 61 | + h = H / 3.0 |
| 62 | + w = W / 2.0 |
| 63 | + alpha = atan2(h, w) |
| 64 | + b = sqrt(w * w + h * h) |
| 65 | + s = b / sin(alpha) |
| 66 | + c = s - h |
| 67 | + R = c + H |
| 68 | + |
| 69 | + return r, R, c |
| 70 | + |
| 71 | + |
| 72 | +def R(w, h): |
| 73 | + """ |
| 74 | + calculate the y poistion of the center circle of a basket arch given half the width and the height. |
| 75 | + """ |
| 76 | + a = h / 3.0 |
| 77 | + beta = atan2(a, w) |
| 78 | + c = sqrt(a**2 + w**2) |
| 79 | + d = c / 2.0 |
| 80 | + e = d / sin(beta) |
| 81 | + return e - a |
| 82 | + |
| 83 | + |
| 84 | +def c(xA, yA, rA, xB, yB, rB): |
| 85 | + """ |
| 86 | + return the interection point(s) of two circles. |
| 87 | + """ |
| 88 | + d2 = (xB - xA) ** 2 + (yB - yA) ** 2 |
| 89 | + K = (1 / 4.0) * sqrt(((rA + rB) ** 2 - d2) * (d2 - (rA - rB) ** 2)) |
| 90 | + |
| 91 | + xb = (1 / 2.0) * (xB + xA) + (1 / 2.0) * (xB - xA) * (rA**2 - rB**2) / d2 |
| 92 | + dx = 2 * (yB - yA) * K / d2 |
| 93 | + yb = (1 / 2.0) * (yB + yA) + (1 / 2.0) * (yB - yA) * (rA**2 - rB**2) / d2 |
| 94 | + dy = -2 * (xB - xA) * K / d2 |
| 95 | + |
| 96 | + return ((xb + dx, yb + dy), (xb - dx, yb - dy)) |
| 97 | + |
| 98 | + |
| 99 | +def basket_arch(W, D, H=0, resolution=1): |
| 100 | + if (H > 0) and (W / H >= 2.0) and (W / H <= 4.0): |
| 101 | + w = 2 * H / 3.0 |
| 102 | + s = W / 2.0 - w |
| 103 | + rc = R(s, H) |
| 104 | + r = rc + H |
| 105 | + c1x = -s |
| 106 | + c1y = 0 |
| 107 | + cmx = 0 |
| 108 | + cmy = -rc |
| 109 | + c2x = s |
| 110 | + c2y = 0 |
| 111 | + CX, CY = c(c1x, c2x, w, cmx, cmy, r)[0] |
| 112 | + dx = -CX |
| 113 | + dy = CY + rc |
| 114 | + beta = atan2(dx, dy) |
| 115 | + dx = -CX - s |
| 116 | + dy = CY |
| 117 | + alpha = atan2(dx, dy) |
| 118 | + |
| 119 | + else: |
| 120 | + w = W / 4.0 |
| 121 | + h = sqrt(3.0) * w |
| 122 | + r = 3.0 * w |
| 123 | + c1x = -w |
| 124 | + c1y = 0 |
| 125 | + cmx = 0 |
| 126 | + cmy = -h |
| 127 | + c2x = w |
| 128 | + c2y = 0 |
| 129 | + # alpha = atan2(h, 2.0 * w) |
| 130 | + beta = atan2(w, h) |
| 131 | + alpha = beta |
| 132 | + |
| 133 | + co = circle(c1x, c1y, w, pi, pi / 2.0 + alpha, resolution * 3 * pi / 360) |
| 134 | + co += circle(cmx, cmy, r, pi / 2.0 + beta, pi / 2.0 - beta, resolution * pi / 360) |
| 135 | + # reversing the generation of the points in this segment will ensure these segments are symmetrical |
| 136 | + # reversing the resulting list is necessary to maintain the order of the generated polygons |
| 137 | + co += reversed( |
| 138 | + circle(c2x, c2y, w, 0.0, pi / 2.0 - alpha, resolution * 3 * pi / 360) |
| 139 | + ) |
| 140 | + |
| 141 | + n = len(co) |
| 142 | + return co + [(x, y + D, z) for x, y, z in co], [ |
| 143 | + (i, i + 1, n + i + 1, n + i) for i in range(n - 1) |
| 144 | + ] |
| 145 | + |
| 146 | + |
| 147 | +class BasketArch(bpy.types.Operator, object_utils.AddObjectHelper): |
| 148 | + bl_idname = "mesh.basketarch" |
| 149 | + bl_label = "Create a basket arch" |
| 150 | + bl_options = {"REGISTER", "UNDO"} |
| 151 | + |
| 152 | + width: FloatProperty( |
| 153 | + name="Width", |
| 154 | + description="Width of the basket arch (span)", |
| 155 | + default=4, |
| 156 | + subtype="DISTANCE", |
| 157 | + unit="LENGTH", |
| 158 | + ) |
| 159 | + height: FloatProperty( |
| 160 | + name="Height", |
| 161 | + description="Height of the basket arch (0 if classical)", |
| 162 | + default=0, |
| 163 | + subtype="DISTANCE", |
| 164 | + unit="LENGTH", |
| 165 | + ) |
| 166 | + depth: FloatProperty( |
| 167 | + name="Depth", |
| 168 | + description="Depth of the basket arch", |
| 169 | + default=1, |
| 170 | + subtype="DISTANCE", |
| 171 | + unit="LENGTH", |
| 172 | + ) |
| 173 | + resolution: IntProperty( |
| 174 | + name="Resolution", description="Higher values = less polygons", default=1, min=1 |
| 175 | + ) |
| 176 | + |
| 177 | + def execute(self, context): |
| 178 | + |
| 179 | + verts_loc, faces = basket_arch( |
| 180 | + self.width, self.depth, self.height, self.resolution |
| 181 | + ) |
| 182 | + |
| 183 | + mesh = bpy.data.meshes.new("BasketArch") |
| 184 | + |
| 185 | + bm = bmesh.new() |
| 186 | + |
| 187 | + for v_co in verts_loc: |
| 188 | + bm.verts.new(v_co) |
| 189 | + |
| 190 | + # see http://blenderartists.org/forum/archive/index.php/t-354412.html for next bit |
| 191 | + if hasattr(bm.verts, "ensure_lookup_table"): |
| 192 | + bm.verts.ensure_lookup_table() |
| 193 | + |
| 194 | + for f_idx in faces: |
| 195 | + bm.faces.new([bm.verts[i] for i in f_idx]) |
| 196 | + |
| 197 | + bm.to_mesh(mesh) |
| 198 | + mesh.update() |
| 199 | + |
| 200 | + object_utils.object_data_add(context, mesh, operator=self) |
| 201 | + |
| 202 | + return {"FINISHED"} |
| 203 | + |
182 | 204 |
|
183 | 205 | def menu_func(self, context):
|
184 |
| - self.layout.operator(BasketArch.bl_idname, text="Create a basket arch mesh", |
185 |
| - icon='PLUGIN') |
| 206 | + self.layout.operator( |
| 207 | + BasketArch.bl_idname, text="Create a basket arch mesh", icon="PLUGIN" |
| 208 | + ) |
| 209 | + |
186 | 210 |
|
187 | 211 | def register():
|
188 |
| - bpy.utils.register_class(BasketArch) |
189 |
| - bpy.types.VIEW3D_MT_mesh_add.append(menu_func) |
| 212 | + bpy.utils.register_class(BasketArch) |
| 213 | + bpy.types.VIEW3D_MT_mesh_add.append(menu_func) |
190 | 214 |
|
191 | 215 |
|
192 | 216 | def unregister():
|
193 |
| - bpy.types.VIEW3D_MT_mesh_add.remove(menu_func) |
194 |
| - bpy.utils.unregister_class(BasketArch) |
| 217 | + bpy.types.VIEW3D_MT_mesh_add.remove(menu_func) |
| 218 | + bpy.utils.unregister_class(BasketArch) |
0 commit comments