# 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"}