mirror of
https://github.com/blender/blender-addons.git
synced 2025-07-29 12:05:36 +00:00
320 lines
10 KiB
Python
320 lines
10 KiB
Python
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
# Author: Sjaak-de-Draak
|
|
|
|
bl_info = {
|
|
"name": "Triangles",
|
|
"description": "Create different types of triangles",
|
|
"author": "Sjaak-de-Draak",
|
|
"version": (1, 0, 1),
|
|
"blender": (2, 68, 0),
|
|
"location": "View3D > Add > Mesh",
|
|
"warning": "First Version",
|
|
"doc_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
|
|
"Scripts/Triangles",
|
|
"category": "Add Mesh",
|
|
}
|
|
|
|
"""
|
|
This script provides a triangle mesh primitive
|
|
and a toolbar menu to further specify settings
|
|
"""
|
|
|
|
import math
|
|
import bpy
|
|
from mathutils import Vector
|
|
from bpy.types import Operator
|
|
from bpy.props import (
|
|
BoolProperty,
|
|
EnumProperty,
|
|
FloatProperty,
|
|
)
|
|
|
|
|
|
def checkEditMode():
|
|
# Check if we are in edit mode
|
|
# Returns: 1 if True
|
|
# 0 if False
|
|
if (bpy.context.active_object.mode == 'EDIT'):
|
|
return 1
|
|
return 0
|
|
|
|
|
|
def exitEditMode():
|
|
# Check if we are in edit mode (cuz we don't want this when creating a new Mesh)
|
|
# If we are then toggle back to object mode
|
|
# Check if there are active objects
|
|
if bpy.context.active_object is not None:
|
|
# Only the active object should be in edit mode
|
|
if (bpy.context.active_object.mode == 'EDIT'):
|
|
bpy.ops.object.editmode_toggle()
|
|
|
|
|
|
class MakeTriangle(Operator):
|
|
bl_idname = "mesh.make_triangle"
|
|
bl_label = "Triangle"
|
|
bl_description = "Construct different types of Triangle Meshes"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
nothing = 0
|
|
Ya = 0.0
|
|
Xb = 0.0
|
|
Xc = 0.0
|
|
Vertices = []
|
|
Faces = []
|
|
|
|
triangleTypeList = [
|
|
('ISOSCELES', "Isosceles", "Two equal sides", 0),
|
|
('EQUILATERAL', "Equilateral", "Three equal sides and angles (60°)", 1),
|
|
('ISOSCELESRIGHTANGLE', "Isosceles right angled", "90° angle and two equal sides", 2),
|
|
('SCALENERIGHTANGLE', "Scalene right angled", "90° angle, no equal sides", 3)
|
|
]
|
|
triangleFaceList = [
|
|
('DEFAULT', "Normal", "1 Tri(angle) face", 0),
|
|
('TRIANGLES', "3 Tri faces", "4 Vertices & 3 Tri(angle) faces", 1),
|
|
('QUADS', "3 Quad faces", "7 Vertices & 3 Quad faces", 2),
|
|
('SAFEQUADS', "6 Quad faces", "12 Vertices & 6 Quad faces", 3)
|
|
]
|
|
|
|
# add definitions for some manipulation buttons
|
|
flipX: BoolProperty(
|
|
name="Flip X sign",
|
|
description="Draw on the other side of the X axis (Mirror on Y axis)",
|
|
default=False
|
|
)
|
|
flipY: BoolProperty(
|
|
name="Flip Y sign",
|
|
description="Draw on the other side of the Y axis (Mirror on X axis)",
|
|
default=False
|
|
)
|
|
scale: FloatProperty(
|
|
name="Scale",
|
|
description="Triangle scale",
|
|
default=1.0,
|
|
min=1.0
|
|
)
|
|
triangleType: EnumProperty(
|
|
items=triangleTypeList,
|
|
name="Type",
|
|
description="Triangle Type"
|
|
)
|
|
triangleFace: EnumProperty(
|
|
items=triangleFaceList,
|
|
name="Face types",
|
|
description="Triangle Face Types"
|
|
)
|
|
at_3Dcursor: BoolProperty(
|
|
name="Use 3D Cursor",
|
|
description="Draw the triangle where the 3D cursor is",
|
|
default=False
|
|
)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
col = layout.column(align=True)
|
|
col.prop(self, "triangleType", text="Type")
|
|
col.prop(self, "scale")
|
|
col.prop(self, "triangleFace", text="Face")
|
|
|
|
col = layout.column(align=True)
|
|
col.prop(self, "at_3Dcursor", text="3D Cursor", toggle=True)
|
|
|
|
row = col.row(align=True)
|
|
row.prop(self, "flipX", toggle=True)
|
|
row.prop(self, "flipY", toggle=True)
|
|
|
|
def drawBasicTriangleShape(self):
|
|
# set everything to 0
|
|
Xb = Xc = 0.0
|
|
Ya = 0.0
|
|
|
|
scale = self.scale
|
|
Xsign = -1 if self.flipX else 1
|
|
Ysign = -1 if self.flipY else 1
|
|
|
|
# Isosceles (2 equal sides)
|
|
if (self.triangleType == 'ISOSCELES'):
|
|
# below a simple triangle containing 2 triangles with 1:2 side ratio
|
|
Ya = (1 * Ysign * scale)
|
|
A = Vector([0.0, Ya, 0.0])
|
|
Xb = (0.5 * Xsign * scale)
|
|
B = Vector([Xb, 0.0, 0.0])
|
|
Xc = (-0.5 * Xsign * scale)
|
|
C = Vector([Xc, 0.0, 0.0])
|
|
|
|
self.Ya = Ya
|
|
self.Xb = Xb
|
|
self.Xc = Xc
|
|
self.Vertices = [A, B, C, ]
|
|
|
|
return True
|
|
|
|
# Equilateral (all sides equal)
|
|
if (self.triangleType == 'EQUILATERAL'):
|
|
Ya = (math.sqrt(0.75) * Ysign * scale)
|
|
A = Vector([0.0, Ya, 0.0])
|
|
Xb = (0.5 * Xsign * scale)
|
|
B = Vector([Xb, 0.0, 0.0])
|
|
Xc = (-0.5 * Xsign * scale)
|
|
C = Vector([Xc, 0.0, 0.0])
|
|
|
|
self.Ya = Ya
|
|
self.Xb = Xb
|
|
self.Xc = Xc
|
|
self.Vertices = [A, B, C, ]
|
|
|
|
return True
|
|
|
|
# Isosceles right angled (1, 1, sqrt(2))
|
|
if (self.triangleType == 'ISOSCELESRIGHTANGLE'):
|
|
Ya = (1 * Ysign * scale)
|
|
A = Vector([0.0, Ya, 0.0])
|
|
Xb = 0.0
|
|
B = Vector([Xb, 0.0, 0.0])
|
|
Xc = (1 * Xsign * scale)
|
|
C = Vector([Xc, 0.0, 0.0])
|
|
|
|
self.Ya = Ya
|
|
self.Xb = Xb
|
|
self.Xc = Xc
|
|
self.Vertices = [A, B, C, ]
|
|
return True
|
|
|
|
# Scalene right angled (3, 4, 5)
|
|
if (self.triangleType == 'SCALENERIGHTANGLE'):
|
|
Ya = (1 * Ysign * scale)
|
|
A = Vector([0.0, Ya, 0.0])
|
|
Xb = 0
|
|
B = Vector([Xb, 0.0, 0.0])
|
|
Xc = (0.75 * Xsign * scale)
|
|
C = Vector([Xc, 0.0, 0.0])
|
|
|
|
self.Ya = Ya
|
|
self.Xb = Xb
|
|
self.Xc = Xc
|
|
self.Vertices = [A, B, C, ]
|
|
return True
|
|
|
|
return False
|
|
|
|
def addFaces(self, fType=None):
|
|
Ya = self.Ya
|
|
Xb = self.Xb
|
|
Xc = self.Xc
|
|
|
|
if (self.triangleFace == 'DEFAULT'):
|
|
self.Faces = [[0, 1, 2]]
|
|
return True
|
|
|
|
if (self.triangleFace == 'TRIANGLES'):
|
|
A = Vector([0.0, Ya, 0.0])
|
|
B = Vector([Xb, 0.0, 0.0])
|
|
C = Vector([Xc, 0.0, 0.0])
|
|
D = Vector([((A.x + B.x + C.x) / 3), ((A.y + B.y + C.y) / 3), ((A.z + B.z + C.z) / 3)])
|
|
|
|
self.Vertices = [A, B, C, D, ]
|
|
self.Faces = [[0, 1, 3], [1, 2, 3], [2, 0, 3]]
|
|
return True
|
|
|
|
if (self.triangleFace == 'QUADS'):
|
|
A = Vector([0.0, Ya, 0.0])
|
|
B = Vector([Xb, 0.0, 0.0])
|
|
C = Vector([Xc, 0.0, 0.0])
|
|
D = Vector([((A.x + B.x + C.x) / 3), ((A.y + B.y + C.y) / 3), ((A.z + B.z + C.z) / 3)])
|
|
AB = A.lerp(B, 0.5)
|
|
AC = A.lerp(C, 0.5)
|
|
BC = B.lerp(C, 0.5)
|
|
|
|
self.Vertices = [A, AB, B, BC, C, AC, D, ]
|
|
self.Faces = [[0, 1, 6, 5], [1, 2, 3, 6], [3, 4, 5, 6]]
|
|
return True
|
|
|
|
if (self.triangleFace == 'SAFEQUADS'):
|
|
A = Vector([0.0, Ya, 0.0])
|
|
B = Vector([Xb, 0.0, 0.0])
|
|
C = Vector([Xc, 0.0, 0.0])
|
|
D = Vector([((A.x + B.x + C.x) / 3), ((A.y + B.y + C.y) / 3), ((A.z + B.z + C.z) / 3)])
|
|
E = A.lerp(D, 0.5)
|
|
AB = A.lerp(B, 0.5)
|
|
AC = A.lerp(C, 0.5)
|
|
BC = B.lerp(C, 0.5)
|
|
AAB = AB.lerp(A, 0.5)
|
|
AAC = AC.lerp(A, 0.5)
|
|
BBA = AB.lerp(B, 0.5)
|
|
BBC = BC.lerp(B, 0.5)
|
|
BCC = BC.lerp(C, 0.5)
|
|
CCA = AC.lerp(C, 0.5)
|
|
|
|
self.Vertices = [A, AAB, BBA, B, BBC, BC, BCC, C, CCA, AAC, D, E, ]
|
|
self.Faces = [[0, 1, 11, 9], [1, 2, 10, 11], [2, 3, 4, 10],
|
|
[4, 5, 6, 10], [6, 7, 8, 10], [8, 9, 11, 10]]
|
|
return True
|
|
|
|
return False
|
|
|
|
def action_common(self, context):
|
|
# definitions:
|
|
# a triangle consists of 3 points: A, B, C
|
|
# a 'safer' subdividable triangle consists of 4 points: A, B, C, D
|
|
# a subdivide friendly triangle consists of 7 points: A, B, C, D, AB, AC, BC
|
|
# a truly subdivide friendly triangle consists of (3 x 4 = )12 points:
|
|
# A, B, C, D, E, BC, AAB, AAC, BBA, BBC, BCC, CCA
|
|
|
|
BasicShapeCreated = False
|
|
ShapeFacesCreated = False
|
|
go = 0
|
|
|
|
#
|
|
# call the functions for creating the triangles and test if successful
|
|
#
|
|
BasicShapeCreated = self.drawBasicTriangleShape()
|
|
if (BasicShapeCreated):
|
|
ShapeFacesCreated = self.addFaces()
|
|
if ShapeFacesCreated:
|
|
go = 1
|
|
|
|
if (go == 1):
|
|
NewMesh = bpy.data.meshes.new("Triangle")
|
|
NewMesh.from_pydata(self.Vertices, [], self.Faces)
|
|
|
|
NewMesh.update()
|
|
NewObj = bpy.data.objects.new("Triangle", NewMesh)
|
|
context.collection.objects.link(NewObj)
|
|
|
|
# before doing the deselect make sure edit mode isn't active
|
|
exitEditMode()
|
|
bpy.ops.object.select_all(action="DESELECT")
|
|
NewObj.select_set(True)
|
|
context.view_layer.objects.active = NewObj
|
|
|
|
if self.at_3Dcursor is True:
|
|
# we'll need to be sure there is actually an object selected
|
|
if NewObj.select_get() is True:
|
|
# we also have to check if we're considered to be in 3D View (view3d)
|
|
if bpy.ops.view3d.snap_selected_to_cursor.poll() is True:
|
|
bpy.ops.view3d.snap_selected_to_cursor()
|
|
else:
|
|
# as we weren't considered to be in 3D View
|
|
# the object couldn't be moved to the 3D cursor
|
|
# so to avoid confusion we change the at_3Dcursor boolean to false
|
|
self.at_3Dcursor = False
|
|
|
|
else:
|
|
self.report({'WARNING'},
|
|
"Triangle could not be completed. (See Console for more Info)")
|
|
|
|
print("\n[Add Mesh Extra Objects]\n\nModule: add_mesh_triangle")
|
|
print("Triangle type: %s\n" % self.triangleType,
|
|
"Face type: %s\n" % self.triangleFace,
|
|
"Ya: %s, Xb: %s, Xc: %s\n" % (self.Ya, self.Xb, self.Xc),
|
|
"Vertices: %s\n" % self.Vertices,
|
|
"Faces: %s\n" % self.Faces)
|
|
|
|
def execute(self, context):
|
|
self.action_common(context)
|
|
return {"FINISHED"}
|
|
|
|
def invoke(self, context, event):
|
|
self.action_common(context)
|
|
return {"FINISHED"}
|