1 # SPDX-FileCopyrightText: 2017-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
7 "description": "Create different types of triangles",
8 "author": "Sjaak-de-Draak",
10 "blender": (2, 68, 0),
11 "location": "View3D > Add > Mesh",
12 "warning": "First Version",
13 "doc_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
15 "category": "Add Mesh",
19 This script provides a triangle mesh primitive
20 and a toolbar menu to further specify settings
25 from mathutils
import Vector
26 from bpy
.types
import Operator
27 from bpy
.props
import (
35 # Check if we are in edit mode
38 if (bpy
.context
.active_object
.mode
== 'EDIT'):
44 # Check if we are in edit mode (cuz we don't want this when creating a new Mesh)
45 # If we are then toggle back to object mode
46 # Check if there are active objects
47 if bpy
.context
.active_object
is not None:
48 # Only the active object should be in edit mode
49 if (bpy
.context
.active_object
.mode
== 'EDIT'):
50 bpy
.ops
.object.editmode_toggle()
53 class MakeTriangle(Operator
):
54 bl_idname
= "mesh.make_triangle"
56 bl_description
= "Construct different types of Triangle Meshes"
57 bl_options
= {"REGISTER", "UNDO"}
67 ('ISOSCELES', "Isosceles", "Two equal sides", 0),
68 ('EQUILATERAL', "Equilateral", "Three equal sides and angles (60°)", 1),
69 ('ISOSCELESRIGHTANGLE', "Isosceles right angled", "90° angle and two equal sides", 2),
70 ('SCALENERIGHTANGLE', "Scalene right angled", "90° angle, no equal sides", 3)
73 ('DEFAULT', "Normal", "1 Tri(angle) face", 0),
74 ('TRIANGLES', "3 Tri faces", "4 Vertices & 3 Tri(angle) faces", 1),
75 ('QUADS', "3 Quad faces", "7 Vertices & 3 Quad faces", 2),
76 ('SAFEQUADS', "6 Quad faces", "12 Vertices & 6 Quad faces", 3)
79 # add definitions for some manipulation buttons
82 description
="Draw on the other side of the X axis (Mirror on Y axis)",
87 description
="Draw on the other side of the Y axis (Mirror on X axis)",
92 description
="Triangle scale",
96 triangleType
: EnumProperty(
97 items
=triangleTypeList
,
99 description
="Triangle Type"
101 triangleFace
: EnumProperty(
102 items
=triangleFaceList
,
104 description
="Triangle Face Types"
106 at_3Dcursor
: BoolProperty(
107 name
="Use 3D Cursor",
108 description
="Draw the triangle where the 3D cursor is",
112 def draw(self
, context
):
115 col
= layout
.column(align
=True)
116 col
.prop(self
, "triangleType", text
="Type")
117 col
.prop(self
, "scale")
118 col
.prop(self
, "triangleFace", text
="Face")
120 col
= layout
.column(align
=True)
121 col
.prop(self
, "at_3Dcursor", text
="3D Cursor", toggle
=True)
123 row
= col
.row(align
=True)
124 row
.prop(self
, "flipX", toggle
=True)
125 row
.prop(self
, "flipY", toggle
=True)
127 def drawBasicTriangleShape(self
):
128 # set everything to 0
133 Xsign
= -1 if self
.flipX
else 1
134 Ysign
= -1 if self
.flipY
else 1
136 # Isosceles (2 equal sides)
137 if (self
.triangleType
== 'ISOSCELES'):
138 # below a simple triangle containing 2 triangles with 1:2 side ratio
139 Ya
= (1 * Ysign
* scale
)
140 A
= Vector([0.0, Ya
, 0.0])
141 Xb
= (0.5 * Xsign
* scale
)
142 B
= Vector([Xb
, 0.0, 0.0])
143 Xc
= (-0.5 * Xsign
* scale
)
144 C
= Vector([Xc
, 0.0, 0.0])
149 self
.Vertices
= [A
, B
, C
, ]
153 # Equilateral (all sides equal)
154 if (self
.triangleType
== 'EQUILATERAL'):
155 Ya
= (math
.sqrt(0.75) * Ysign
* scale
)
156 A
= Vector([0.0, Ya
, 0.0])
157 Xb
= (0.5 * Xsign
* scale
)
158 B
= Vector([Xb
, 0.0, 0.0])
159 Xc
= (-0.5 * Xsign
* scale
)
160 C
= Vector([Xc
, 0.0, 0.0])
165 self
.Vertices
= [A
, B
, C
, ]
169 # Isosceles right angled (1, 1, sqrt(2))
170 if (self
.triangleType
== 'ISOSCELESRIGHTANGLE'):
171 Ya
= (1 * Ysign
* scale
)
172 A
= Vector([0.0, Ya
, 0.0])
174 B
= Vector([Xb
, 0.0, 0.0])
175 Xc
= (1 * Xsign
* scale
)
176 C
= Vector([Xc
, 0.0, 0.0])
181 self
.Vertices
= [A
, B
, C
, ]
184 # Scalene right angled (3, 4, 5)
185 if (self
.triangleType
== 'SCALENERIGHTANGLE'):
186 Ya
= (1 * Ysign
* scale
)
187 A
= Vector([0.0, Ya
, 0.0])
189 B
= Vector([Xb
, 0.0, 0.0])
190 Xc
= (0.75 * Xsign
* scale
)
191 C
= Vector([Xc
, 0.0, 0.0])
196 self
.Vertices
= [A
, B
, C
, ]
201 def addFaces(self
, fType
=None):
206 if (self
.triangleFace
== 'DEFAULT'):
207 self
.Faces
= [[0, 1, 2]]
210 if (self
.triangleFace
== 'TRIANGLES'):
211 A
= Vector([0.0, Ya
, 0.0])
212 B
= Vector([Xb
, 0.0, 0.0])
213 C
= Vector([Xc
, 0.0, 0.0])
214 D
= Vector([((A
.x
+ B
.x
+ C
.x
) / 3), ((A
.y
+ B
.y
+ C
.y
) / 3), ((A
.z
+ B
.z
+ C
.z
) / 3)])
216 self
.Vertices
= [A
, B
, C
, D
, ]
217 self
.Faces
= [[0, 1, 3], [1, 2, 3], [2, 0, 3]]
220 if (self
.triangleFace
== 'QUADS'):
221 A
= Vector([0.0, Ya
, 0.0])
222 B
= Vector([Xb
, 0.0, 0.0])
223 C
= Vector([Xc
, 0.0, 0.0])
224 D
= Vector([((A
.x
+ B
.x
+ C
.x
) / 3), ((A
.y
+ B
.y
+ C
.y
) / 3), ((A
.z
+ B
.z
+ C
.z
) / 3)])
229 self
.Vertices
= [A
, AB
, B
, BC
, C
, AC
, D
, ]
230 self
.Faces
= [[0, 1, 6, 5], [1, 2, 3, 6], [3, 4, 5, 6]]
233 if (self
.triangleFace
== 'SAFEQUADS'):
234 A
= Vector([0.0, Ya
, 0.0])
235 B
= Vector([Xb
, 0.0, 0.0])
236 C
= Vector([Xc
, 0.0, 0.0])
237 D
= Vector([((A
.x
+ B
.x
+ C
.x
) / 3), ((A
.y
+ B
.y
+ C
.y
) / 3), ((A
.z
+ B
.z
+ C
.z
) / 3)])
242 AAB
= AB
.lerp(A
, 0.5)
243 AAC
= AC
.lerp(A
, 0.5)
244 BBA
= AB
.lerp(B
, 0.5)
245 BBC
= BC
.lerp(B
, 0.5)
246 BCC
= BC
.lerp(C
, 0.5)
247 CCA
= AC
.lerp(C
, 0.5)
249 self
.Vertices
= [A
, AAB
, BBA
, B
, BBC
, BC
, BCC
, C
, CCA
, AAC
, D
, E
, ]
250 self
.Faces
= [[0, 1, 11, 9], [1, 2, 10, 11], [2, 3, 4, 10],
251 [4, 5, 6, 10], [6, 7, 8, 10], [8, 9, 11, 10]]
256 def action_common(self
, context
):
258 # a triangle consists of 3 points: A, B, C
259 # a 'safer' subdividable triangle consists of 4 points: A, B, C, D
260 # a subdivide friendly triangle consists of 7 points: A, B, C, D, AB, AC, BC
261 # a truly subdivide friendly triangle consists of (3 x 4 = )12 points:
262 # A, B, C, D, E, BC, AAB, AAC, BBA, BBC, BCC, CCA
264 BasicShapeCreated
= False
265 ShapeFacesCreated
= False
269 # call the functions for creating the triangles and test if successful
271 BasicShapeCreated
= self
.drawBasicTriangleShape()
272 if (BasicShapeCreated
):
273 ShapeFacesCreated
= self
.addFaces()
274 if ShapeFacesCreated
:
278 NewMesh
= bpy
.data
.meshes
.new("Triangle")
279 NewMesh
.from_pydata(self
.Vertices
, [], self
.Faces
)
282 NewObj
= bpy
.data
.objects
.new("Triangle", NewMesh
)
283 context
.collection
.objects
.link(NewObj
)
285 # before doing the deselect make sure edit mode isn't active
287 bpy
.ops
.object.select_all(action
="DESELECT")
288 NewObj
.select_set(True)
289 context
.view_layer
.objects
.active
= NewObj
291 if self
.at_3Dcursor
is True:
292 # we'll need to be sure there is actually an object selected
293 if NewObj
.select_get() is True:
294 # we also have to check if we're considered to be in 3D View (view3d)
295 if bpy
.ops
.view3d
.snap_selected_to_cursor
.poll() is True:
296 bpy
.ops
.view3d
.snap_selected_to_cursor()
298 # as we weren't considered to be in 3D View
299 # the object couldn't be moved to the 3D cursor
300 # so to avoid confusion we change the at_3Dcursor boolean to false
301 self
.at_3Dcursor
= False
304 self
.report({'WARNING'},
305 "Triangle could not be completed. (See Console for more Info)")
307 print("\n[Add Mesh Extra Objects]\n\nModule: add_mesh_triangle")
308 print("Triangle type: %s\n" % self
.triangleType
,
309 "Face type: %s\n" % self
.triangleFace
,
310 "Ya: %s, Xb: %s, Xc: %s\n" % (self
.Ya
, self
.Xb
, self
.Xc
),
311 "Vertices: %s\n" % self
.Vertices
,
312 "Faces: %s\n" % self
.Faces
)
314 def execute(self
, context
):
315 self
.action_common(context
)
318 def invoke(self
, context
, event
):
319 self
.action_common(context
)