Cleanup: trailing space
[blender-addons.git] / add_mesh_extra_objects / add_mesh_triangles.py
blobf0a45ba862f69b5aedd861f7c0e3598a1f33a00d
1 # SPDX-License-Identifier: GPL-2.0-or-later
2 # Author: Sjaak-de-Draak
4 bl_info = {
5 "name": "Triangles",
6 "description": "Create different types of triangles",
7 "author": "Sjaak-de-Draak",
8 "version": (1, 0, 1),
9 "blender": (2, 68, 0),
10 "location": "View3D > Add > Mesh",
11 "warning": "First Version",
12 "doc_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
13 "Scripts/Triangles",
14 "category": "Add Mesh",
17 """
18 This script provides a triangle mesh primitive
19 and a toolbar menu to further specify settings
20 """
22 import math
23 import bpy
24 from mathutils import Vector
25 from bpy.types import Operator
26 from bpy.props import (
27 BoolProperty,
28 EnumProperty,
29 FloatProperty,
33 def checkEditMode():
34 # Check if we are in edit mode
35 # Returns: 1 if True
36 # 0 if False
37 if (bpy.context.active_object.mode == 'EDIT'):
38 return 1
39 return 0
42 def exitEditMode():
43 # Check if we are in edit mode (cuz we don't want this when creating a new Mesh)
44 # If we are then toggle back to object mode
45 # Check if there are active objects
46 if bpy.context.active_object is not None:
47 # Only the active object should be in edit mode
48 if (bpy.context.active_object.mode == 'EDIT'):
49 bpy.ops.object.editmode_toggle()
52 class MakeTriangle(Operator):
53 bl_idname = "mesh.make_triangle"
54 bl_label = "Triangle"
55 bl_description = "Construct different types of Triangle Meshes"
56 bl_options = {"REGISTER", "UNDO"}
58 nothing = 0
59 Ya = 0.0
60 Xb = 0.0
61 Xc = 0.0
62 Vertices = []
63 Faces = []
65 triangleTypeList = [
66 ('ISOSCELES', "Isosceles", "Two equal sides", 0),
67 ('EQUILATERAL', "Equilateral", "Three equal sides and angles (60°)", 1),
68 ('ISOSCELESRIGHTANGLE', "Isosceles right angled", "90° angle and two equal sides", 2),
69 ('SCALENERIGHTANGLE', "Scalene right angled", "90° angle, no equal sides", 3)
71 triangleFaceList = [
72 ('DEFAULT', "Normal", "1 Tri(angle) face", 0),
73 ('TRIANGLES', "3 Tri faces", "4 Vertices & 3 Tri(angle) faces", 1),
74 ('QUADS', "3 Quad faces", "7 Vertices & 3 Quad faces", 2),
75 ('SAFEQUADS', "6 Quad faces", "12 Vertices & 6 Quad faces", 3)
78 # add definitions for some manipulation buttons
79 flipX: BoolProperty(
80 name="Flip X sign",
81 description="Draw on the other side of the X axis (Mirror on Y axis)",
82 default=False
84 flipY: BoolProperty(
85 name="Flip Y sign",
86 description="Draw on the other side of the Y axis (Mirror on X axis)",
87 default=False
89 scale: FloatProperty(
90 name="Scale",
91 description="Triangle scale",
92 default=1.0,
93 min=1.0
95 triangleType: EnumProperty(
96 items=triangleTypeList,
97 name="Type",
98 description="Triangle Type"
100 triangleFace: EnumProperty(
101 items=triangleFaceList,
102 name="Face types",
103 description="Triangle Face Types"
105 at_3Dcursor: BoolProperty(
106 name="Use 3D Cursor",
107 description="Draw the triangle where the 3D cursor is",
108 default=False
111 def draw(self, context):
112 layout = self.layout
114 col = layout.column(align=True)
115 col.prop(self, "triangleType", text="Type")
116 col.prop(self, "scale")
117 col.prop(self, "triangleFace", text="Face")
119 col = layout.column(align=True)
120 col.prop(self, "at_3Dcursor", text="3D Cursor", toggle=True)
122 row = col.row(align=True)
123 row.prop(self, "flipX", toggle=True)
124 row.prop(self, "flipY", toggle=True)
126 def drawBasicTriangleShape(self):
127 # set everything to 0
128 Xb = Xc = 0.0
129 Ya = 0.0
131 scale = self.scale
132 Xsign = -1 if self.flipX else 1
133 Ysign = -1 if self.flipY else 1
135 # Isosceles (2 equal sides)
136 if (self.triangleType == 'ISOSCELES'):
137 # below a simple triangle containing 2 triangles with 1:2 side ratio
138 Ya = (1 * Ysign * scale)
139 A = Vector([0.0, Ya, 0.0])
140 Xb = (0.5 * Xsign * scale)
141 B = Vector([Xb, 0.0, 0.0])
142 Xc = (-0.5 * Xsign * scale)
143 C = Vector([Xc, 0.0, 0.0])
145 self.Ya = Ya
146 self.Xb = Xb
147 self.Xc = Xc
148 self.Vertices = [A, B, C, ]
150 return True
152 # Equilateral (all sides equal)
153 if (self.triangleType == 'EQUILATERAL'):
154 Ya = (math.sqrt(0.75) * Ysign * scale)
155 A = Vector([0.0, Ya, 0.0])
156 Xb = (0.5 * Xsign * scale)
157 B = Vector([Xb, 0.0, 0.0])
158 Xc = (-0.5 * Xsign * scale)
159 C = Vector([Xc, 0.0, 0.0])
161 self.Ya = Ya
162 self.Xb = Xb
163 self.Xc = Xc
164 self.Vertices = [A, B, C, ]
166 return True
168 # Isosceles right angled (1, 1, sqrt(2))
169 if (self.triangleType == 'ISOSCELESRIGHTANGLE'):
170 Ya = (1 * Ysign * scale)
171 A = Vector([0.0, Ya, 0.0])
172 Xb = 0.0
173 B = Vector([Xb, 0.0, 0.0])
174 Xc = (1 * Xsign * scale)
175 C = Vector([Xc, 0.0, 0.0])
177 self.Ya = Ya
178 self.Xb = Xb
179 self.Xc = Xc
180 self.Vertices = [A, B, C, ]
181 return True
183 # Scalene right angled (3, 4, 5)
184 if (self.triangleType == 'SCALENERIGHTANGLE'):
185 Ya = (1 * Ysign * scale)
186 A = Vector([0.0, Ya, 0.0])
187 Xb = 0
188 B = Vector([Xb, 0.0, 0.0])
189 Xc = (0.75 * Xsign * scale)
190 C = Vector([Xc, 0.0, 0.0])
192 self.Ya = Ya
193 self.Xb = Xb
194 self.Xc = Xc
195 self.Vertices = [A, B, C, ]
196 return True
198 return False
200 def addFaces(self, fType=None):
201 Ya = self.Ya
202 Xb = self.Xb
203 Xc = self.Xc
205 if (self.triangleFace == 'DEFAULT'):
206 self.Faces = [[0, 1, 2]]
207 return True
209 if (self.triangleFace == 'TRIANGLES'):
210 A = Vector([0.0, Ya, 0.0])
211 B = Vector([Xb, 0.0, 0.0])
212 C = Vector([Xc, 0.0, 0.0])
213 D = Vector([((A.x + B.x + C.x) / 3), ((A.y + B.y + C.y) / 3), ((A.z + B.z + C.z) / 3)])
215 self.Vertices = [A, B, C, D, ]
216 self.Faces = [[0, 1, 3], [1, 2, 3], [2, 0, 3]]
217 return True
219 if (self.triangleFace == 'QUADS'):
220 A = Vector([0.0, Ya, 0.0])
221 B = Vector([Xb, 0.0, 0.0])
222 C = Vector([Xc, 0.0, 0.0])
223 D = Vector([((A.x + B.x + C.x) / 3), ((A.y + B.y + C.y) / 3), ((A.z + B.z + C.z) / 3)])
224 AB = A.lerp(B, 0.5)
225 AC = A.lerp(C, 0.5)
226 BC = B.lerp(C, 0.5)
228 self.Vertices = [A, AB, B, BC, C, AC, D, ]
229 self.Faces = [[0, 1, 6, 5], [1, 2, 3, 6], [3, 4, 5, 6]]
230 return True
232 if (self.triangleFace == 'SAFEQUADS'):
233 A = Vector([0.0, Ya, 0.0])
234 B = Vector([Xb, 0.0, 0.0])
235 C = Vector([Xc, 0.0, 0.0])
236 D = Vector([((A.x + B.x + C.x) / 3), ((A.y + B.y + C.y) / 3), ((A.z + B.z + C.z) / 3)])
237 E = A.lerp(D, 0.5)
238 AB = A.lerp(B, 0.5)
239 AC = A.lerp(C, 0.5)
240 BC = B.lerp(C, 0.5)
241 AAB = AB.lerp(A, 0.5)
242 AAC = AC.lerp(A, 0.5)
243 BBA = AB.lerp(B, 0.5)
244 BBC = BC.lerp(B, 0.5)
245 BCC = BC.lerp(C, 0.5)
246 CCA = AC.lerp(C, 0.5)
248 self.Vertices = [A, AAB, BBA, B, BBC, BC, BCC, C, CCA, AAC, D, E, ]
249 self.Faces = [[0, 1, 11, 9], [1, 2, 10, 11], [2, 3, 4, 10],
250 [4, 5, 6, 10], [6, 7, 8, 10], [8, 9, 11, 10]]
251 return True
253 return False
255 def action_common(self, context):
256 # definitions:
257 # a triangle consists of 3 points: A, B, C
258 # a 'safer' subdividable triangle consists of 4 points: A, B, C, D
259 # a subdivide friendly triangle consists of 7 points: A, B, C, D, AB, AC, BC
260 # a truly subdivide friendly triangle consists of (3 x 4 = )12 points:
261 # A, B, C, D, E, BC, AAB, AAC, BBA, BBC, BCC, CCA
263 BasicShapeCreated = False
264 ShapeFacesCreated = False
265 go = 0
268 # call the functions for creating the triangles and test if successful
270 BasicShapeCreated = self.drawBasicTriangleShape()
271 if (BasicShapeCreated):
272 ShapeFacesCreated = self.addFaces()
273 if ShapeFacesCreated:
274 go = 1
276 if (go == 1):
277 NewMesh = bpy.data.meshes.new("Triangle")
278 NewMesh.from_pydata(self.Vertices, [], self.Faces)
280 NewMesh.update()
281 NewObj = bpy.data.objects.new("Triangle", NewMesh)
282 context.collection.objects.link(NewObj)
284 # before doing the deselect make sure edit mode isn't active
285 exitEditMode()
286 bpy.ops.object.select_all(action="DESELECT")
287 NewObj.select_set(True)
288 context.view_layer.objects.active = NewObj
290 if self.at_3Dcursor is True:
291 # we'll need to be sure there is actually an object selected
292 if NewObj.select_get() is True:
293 # we also have to check if we're considered to be in 3D View (view3d)
294 if bpy.ops.view3d.snap_selected_to_cursor.poll() is True:
295 bpy.ops.view3d.snap_selected_to_cursor()
296 else:
297 # as we weren't considered to be in 3D View
298 # the object couldn't be moved to the 3D cursor
299 # so to avoid confusion we change the at_3Dcursor boolean to false
300 self.at_3Dcursor = False
302 else:
303 self.report({'WARNING'},
304 "Triangle could not be completed. (See Console for more Info)")
306 print("\n[Add Mesh Extra Objects]\n\nModule: add_mesh_triangle")
307 print("Triangle type: %s\n" % self.triangleType,
308 "Face type: %s\n" % self.triangleFace,
309 "Ya: %s, Xb: %s, Xc: %s\n" % (self.Ya, self.Xb, self.Xc),
310 "Vertices: %s\n" % self.Vertices,
311 "Faces: %s\n" % self.Faces)
313 def execute(self, context):
314 self.action_common(context)
315 return {"FINISHED"}
317 def invoke(self, context, event):
318 self.action_common(context)
319 return {"FINISHED"}