├── triangle.png ├── triangle_ui.png ├── mesh_triangle ├── Triangle.dll ├── libtriangle.so ├── triangle.py └── __init__.py ├── LICENSE └── README.md /triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsvilans/bpy_triangle/HEAD/triangle.png -------------------------------------------------------------------------------- /triangle_ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsvilans/bpy_triangle/HEAD/triangle_ui.png -------------------------------------------------------------------------------- /mesh_triangle/Triangle.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsvilans/bpy_triangle/HEAD/mesh_triangle/Triangle.dll -------------------------------------------------------------------------------- /mesh_triangle/libtriangle.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsvilans/bpy_triangle/HEAD/mesh_triangle/libtriangle.so -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Tom Svilans 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bpy_triangle 2 | A Blender add-on for Triangle. 3 | ![Triangle in Blender.](https://raw.githubusercontent.com/tsvilans/bpy_triangle/master/triangle.png) 4 | 5 | ``` 6 | Triangle: A Two-Dimensional Quality Mesh Generator and Delaunay Triangulator 7 | Copyright 1993, 1995, 1997, 1998, 2002, 2005 8 | Jonathan Richard Shewchuk 9 | 2360 Woolsey #H 10 | Berkeley, California 94705-1927 11 | jrs@cs.berkeley.edu 12 | ``` 13 | 14 | http://www.cs.cmu.edu/~quake/triangle.html 15 | 16 | This is a Python wrapper for the Triangle library referenced above. It uses ctypes. 17 | The add-on adds a 'Triangulate' operator to Blender, which can take any planar meshable object 18 | (curves and meshes) and create a triangulated mesh based on specified input parameters. 19 | 20 | Input parameters are provided via a text field and are described in the Triangle documentation. 21 | 22 | ![Triangle in Blender.](https://raw.githubusercontent.com/tsvilans/bpy_triangle/master/triangle_ui.png) 23 | 24 | Further work could be done to expand functionality and add support for meshing Ngon faces or some 25 | other way of operating out of the XY plane, but for now it is limited to this. 26 | 27 | This is especially useful for creating meshes for sculpting or meshes with good face densities 28 | from 2d CAD data, for arch. viz or other uses. Interior edges are respected, meaning creating 29 | dense meshes with interior regions is possible. Note the interior circle in the image above. 30 | 31 | # Update 190514 32 | 33 | - Added Linux support. 34 | - Compiled updated Triangle lib which fixes issues for newer Windows versions: https://github.com/libigl/triangle 35 | 36 | # Update 171208 37 | 38 | - Added UI with props for the most common / useful flags and settings. 39 | - Added vertex group support: triangulated objects have vertex group (Triangle Boundary) which contains the vertices of the input mesh. This makes it easy to define Softbody / Cloth goals, or anchors, or isolate regions. Bear in mind, if input edges are split, the new vertices won't be added to this vertex group. This might be fixed in the future. 40 | 41 | # To do 42 | 43 | - ~~Linux: Compile Triangle for Linux and adjust the script if necessary.~~ 44 | - Find shortest path between ends of split edges, add the new vertices along that path to the Boundary vertex group. 45 | - Find way to expose holes and regions in Blender UI. 46 | 47 | # Contact 48 | 49 | tsvi@kadk.dk 50 | 51 | http://tomsvilans.com 52 | -------------------------------------------------------------------------------- /mesh_triangle/triangle.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2017 Tom Svilans 3 | 4 | http://tomsvilans.com 5 | 6 | A Python wrapper / Blender add-on for Triangle: 7 | 8 | /* A Two-Dimensional Quality Mesh Generator and Delaunay Triangulator. */ 9 | /* (triangle.c) */ 10 | /* */ 11 | /* Version 1.6 */ 12 | /* July 28, 2005 */ 13 | /* */ 14 | /* Copyright 1993, 1995, 1997, 1998, 2002, 2005 */ 15 | /* Jonathan Richard Shewchuk */ 16 | /* 2360 Woolsey #H */ 17 | /* Berkeley, California 94705-1927 */ 18 | /* jrs@cs.berkeley.edu */ 19 | 20 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 21 | software and associated documentation files (the "Software"), to deal in the Software 22 | without restriction, including without limitation the rights to use, copy, modify, merge, 23 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 24 | to whom the Software is furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in all copies 27 | or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 30 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 31 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 32 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 33 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 34 | DEALINGS IN THE SOFTWARE. 35 | 36 | """ 37 | 38 | import sys 39 | 40 | import ctypes, os 41 | from ctypes import POINTER, c_double, c_int, c_char_p, c_void_p 42 | 43 | def triprint(msg): 44 | print("Triangle::" + msg) 45 | 46 | lib_name = "Triangle.dll" 47 | 48 | triprint("System: {}".format(os.name)) 49 | 50 | if os.name == "nt": 51 | lib_name = "Triangle.dll" 52 | elif os.name == "posix": 53 | lib_name = "libtriangle.so" 54 | 55 | lib_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), lib_name) 56 | 57 | triprint("Lib path: {}".format(lib_path)) 58 | lib = ctypes.CDLL(lib_path) 59 | 60 | class TriangleIO(ctypes.Structure): 61 | 62 | _fields_ = [('pointlist', POINTER(c_double)), 63 | ('pointattributelist', POINTER(c_double)), 64 | ('pointmarkerList', POINTER(c_int)), 65 | ('numberofpoints', c_int), 66 | ('numberofpointattributes', c_int), 67 | ('trianglelist', POINTER(c_int)), 68 | ('triangleattributelist', POINTER(c_double)), 69 | ('trianglearealist', POINTER(c_double)), 70 | ('neighborlist', POINTER(c_int)), 71 | ('numberoftriangles', c_int), 72 | ('numberofcorners', c_int), 73 | ('numberoftriangleattributes', c_int), 74 | ('segmentlist', POINTER(c_int)), 75 | ('segmentmarkerlist', POINTER(c_int)), 76 | ('numberofsegments', c_int), 77 | ('holelist', POINTER(c_double)), 78 | ('numberofholes', c_int), 79 | ('regionlist', POINTER(c_double)), 80 | ('numberofregions', c_int), 81 | ('edgelist', POINTER(c_int)), 82 | ('edgemarkerlist', POINTER(c_int)), 83 | ('normlist', POINTER(c_double)), 84 | ('numberofedges', c_int)] 85 | 86 | def __init__(self): 87 | 88 | pointlist = 0 89 | pointattributelist = 0 90 | pointmarkerList = 0 91 | numberofpoints = 0 92 | numberofpointattributes = 0 93 | trianglelist = 0 94 | triangleattributelist = 0 95 | trianglearealist = 0 96 | neighborlist = 0 97 | numberoftriangles = 0 98 | numberofcorners = 0 99 | numberoftriangleattributes = 0 100 | segmentlist = 0 101 | segmentmarkerlist = 0 102 | numberofsegments = 0 103 | holelist = 0 104 | numberofholes = 0 105 | regionlist = 0 106 | numberofregions = 0 107 | edgelist = 0 108 | edgemarkerlist = 0 109 | normlist = 0 110 | numberofedges = 0 111 | 112 | lib.triangulate.argtypes = [c_char_p, POINTER(TriangleIO), POINTER(TriangleIO), POINTER(TriangleIO)] 113 | lib.trifree.argtypes = [c_void_p] 114 | 115 | def createTriangleIO(verts, faces, segments): 116 | io = TriangleIO() 117 | 118 | N = len(verts) 119 | points2d_raw = [] 120 | for i in range(N): 121 | points2d_raw.extend(verts[i][:2]) 122 | 123 | NF = len(faces) 124 | faces_raw = [] 125 | areas = [] 126 | for i in range(NF): 127 | areas.append(0.6) 128 | for j in range(3): 129 | faces_raw.append(faces[i][j]) 130 | # for j in range(len(faces[i])): 131 | # faces_raw.append(faces[i][j]) 132 | 133 | NS = len(segments) 134 | segments_raw = [] 135 | for i in range(NS): 136 | segments_raw.extend(segments[i][:2]) 137 | 138 | pointmarkerlist = [] 139 | for i in range(N): 140 | pointmarkerlist.append(i) 141 | 142 | 143 | segmentmarkerlist = [] 144 | for i in range(NS): 145 | segmentmarkerlist.append(i) 146 | 147 | regionlist = [0.0, 0.0, 0.0, 0.0] 148 | 149 | io.pointlist = (c_double * len(points2d_raw))(*points2d_raw) 150 | io.pointattributelist = None 151 | io.pointmarkerList = (c_int * N)(*pointmarkerlist) 152 | io.numberofpoints = N 153 | io.numberofpointattributes = 0 154 | io.trianglelist = (c_int * (NF * 3))(*faces_raw) 155 | io.triangleattributelist = None 156 | io.trianglearealist = (c_double * NF)(*areas) 157 | io.neighborlist = None 158 | io.numberoftriangles = NF 159 | io.numberofcorners = 3 160 | io.numberoftriangleattributes = 0 161 | io.segmentlist = (c_int * (NS * 2))(*segments_raw) 162 | io.segmentmarkerlist = (c_int * NS)(*segmentmarkerlist) 163 | io.numberofsegments = NS 164 | io.holelist = None 165 | io.numberofholes = 0 166 | #io.regionlist = (c_double * (1 * 4))(*regionlist) 167 | #io.numberofregions = 1 168 | io.regionlist = None 169 | io.numberofregions = 0 170 | io.edgelist = None 171 | io.edgemarkerlist = None 172 | io.normlist = None 173 | io.numberofedges = 0 174 | 175 | return io 176 | 177 | def createMesh(tio): 178 | verts = [] 179 | faces = [] 180 | 181 | N = int(tio.numberofpoints) 182 | ii = 0 183 | for i in range(N): 184 | ii = i * 2 185 | x = float(tio.pointlist[ii]) 186 | y = float(tio.pointlist[ii + 1]) 187 | verts.append((x, y, 0.0)) 188 | 189 | NF = int(tio.numberoftriangles) 190 | triprint ("Number of triangles: %i" % NF) 191 | 192 | for i in range(NF): 193 | ii = i * 3 194 | a = int(tio.trianglelist[ii]) 195 | b = int(tio.trianglelist[ii + 1]) 196 | c = int(tio.trianglelist[ii + 2]) 197 | faces.append((a, b, c)) 198 | 199 | return (verts, faces) 200 | 201 | def triangulate(verts, faces, border, args, vor=False): 202 | in_mesh = createTriangleIO(verts, faces, border) 203 | out_mesh = TriangleIO() 204 | vor_mesh = TriangleIO() 205 | 206 | mutable_string = ctypes.create_string_buffer(str.encode(args)) 207 | res = lib.triangulate(mutable_string, in_mesh, out_mesh, vor_mesh) 208 | 209 | if vor: 210 | return (createMesh(out_mesh), createMesh(vor_mesh)) 211 | return createMesh(out_mesh) 212 | 213 | 214 | if __name__ == '__main__': 215 | 216 | print("Start test...") 217 | #verts = [(0,0,0), (1,0,0), (1,1,0), (0,1,0)] 218 | #faces = [(0, 1, 2), (1, 2, 3)] 219 | #segments = [(0,1), (1,2), (2,3), (3,0)] 220 | #in_mesh = createTriangleIO(verts, faces, segments) 221 | #out_mesh = TriangleIO() 222 | #vor_mesh = TriangleIO() 223 | 224 | #res = lib.triangulate("pczAevn".encode('utf-8'), in_mesh, out_mesh, vor_mesh) 225 | 226 | print("Test") 227 | 228 | -------------------------------------------------------------------------------- /mesh_triangle/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2017 Tom Svilans 3 | 4 | http://tomsvilans.com 5 | 6 | A Python wrapper / Blender add-on for Triangle: 7 | 8 | /* A Two-Dimensional Quality Mesh Generator and Delaunay Triangulator. */ 9 | /* (triangle.c) */ 10 | /* */ 11 | /* Version 1.6 */ 12 | /* July 28, 2005 */ 13 | /* */ 14 | /* Copyright 1993, 1995, 1997, 1998, 2002, 2005 */ 15 | /* Jonathan Richard Shewchuk */ 16 | /* 2360 Woolsey #H */ 17 | /* Berkeley, California 94705-1927 */ 18 | /* jrs@cs.berkeley.edu */ 19 | 20 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 21 | software and associated documentation files (the "Software"), to deal in the Software 22 | without restriction, including without limitation the rights to use, copy, modify, merge, 23 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 24 | to whom the Software is furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in all copies 27 | or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 30 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 31 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 32 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 33 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 34 | DEALINGS IN THE SOFTWARE. 35 | 36 | """ 37 | 38 | bl_info = { 39 | "name": "Triangle", 40 | "author": "Tom Svilans (Blender wrapper)", 41 | "version": (1, 0, 0), 42 | "blender": (2, 79, 0), 43 | "location": "View3D > Toolbar", 44 | "warning": "", 45 | "description": "A two-dimensional quality mesh generator and Delaunay triangulator.", 46 | "wiki_url": "http://www.cs.cmu.edu/~quake/triangle.html" 47 | "Scripts/Modeling/Triangle", 48 | "category": "Mesh", 49 | } 50 | 51 | import bpy, bmesh,os 52 | from mesh_triangle.triangle import triangulate 53 | from bpy.props import StringProperty, BoolProperty, FloatProperty 54 | 55 | def get_nonmanifold_edges(mymesh): 56 | culprits=[] 57 | for e in mymesh.edges: 58 | shared = 0 59 | for f in mymesh.polygons: 60 | for vf1 in f.vertices: 61 | if vf1 == e.vertices[0]: 62 | for vf2 in f.vertices: 63 | if vf2 == e.vertices[1]: 64 | shared = shared + 1 65 | if (shared > 2): 66 | culprits.append((e.vertices[0], e.vertices[1])) 67 | if (shared < 2): 68 | culprits.append((e.vertices[0], e.vertices[1])) 69 | return culprits 70 | 71 | def triangulate_object(obj_in, args): 72 | mesh_in = obj_in.to_mesh(bpy.context.scene, True, 'RENDER') 73 | verts = [x.co for x in mesh_in.vertices] 74 | Nv = len(verts) 75 | faces = [[y for y in x.vertices] for x in mesh_in.polygons] 76 | border = get_nonmanifold_edges(mesh_in) 77 | 78 | #if ('v' in args or 'D' in args): 79 | # return triangulate(verts, faces, border, args, True)[1] 80 | res = triangulate(verts, faces, border, args) 81 | return (res[0], res[1], Nv) 82 | 83 | def add_mesh(verts, faces, mesh_name, obj_name): 84 | bm = bmesh.new() 85 | for v in verts: 86 | bm.verts.new(v) 87 | bm.verts.ensure_lookup_table() 88 | 89 | for f in faces: 90 | bm.faces.new([bm.verts[x] for x in f]) 91 | bm.faces.ensure_lookup_table() 92 | bm.verts.index_update() 93 | 94 | m = bpy.data.meshes.new(mesh_name) 95 | bm.to_mesh(m) 96 | bm.free() 97 | 98 | o = bpy.data.objects.new(obj_name, m) 99 | 100 | return o 101 | 102 | class Triangulate(bpy.types.Operator): 103 | bl_idname = "object.triangulate" 104 | bl_label = "Triangulate (Triangle)" 105 | bl_options = {'REGISTER', 'UNDO'} 106 | 107 | args = StringProperty( 108 | name="Args", 109 | description="Input arguments for Triangle", 110 | default="pq20a1ziV", 111 | ) 112 | 113 | use_args = BoolProperty( 114 | name="Use args", 115 | description="Use command line arg string instead of checkboxes", 116 | default=False) 117 | 118 | cl_p = BoolProperty( 119 | name="PSLG", 120 | description="Triangulates a Planar Straight Line Graph (.poly file)", 121 | default=True) 122 | cl_r = BoolProperty( 123 | name="Refine", 124 | description="Refines a previously generated mesh", 125 | default=False) 126 | cl_q = BoolProperty( 127 | name="Quality", 128 | description="Quality mesh generation with no angles smaller than the specified angle", 129 | default=True) 130 | cl_q_angle = FloatProperty( 131 | name="Quality angle", 132 | description="Angle limit for quality mesh generation", 133 | default=20.0, 134 | max=35.0, 135 | min=0.0) 136 | cl_a = BoolProperty( 137 | name="Area", 138 | description="Imposes a maximum triangle area constraint", 139 | default = False) 140 | cl_a_value = FloatProperty( 141 | name="Area value", 142 | description="Value for area constraint", 143 | default=1.0, 144 | min=0.001) 145 | cl_c = BoolProperty( 146 | name="Convex hull", 147 | description="Encloses the convex hull with segments", 148 | default=False) 149 | cl_D = BoolProperty( 150 | name="Delaunay", 151 | description="Conforming Delaunay: use this switch if you want " \ 152 | "all triangles in the mesh to be Delaunay, and not just constrained " \ 153 | "Delaunay; or if you want to ensure that all Voronoi vertices lie within " \ 154 | "the triangulation", 155 | default=False) 156 | cl_v = BoolProperty( 157 | name="Voronoi", 158 | description="Outputs the Voronoi diagram associated with the triangulation. "\ 159 | "Does not attempt to detect degeneracies, so some Voronoi vertices may be duplicated", 160 | default=False) 161 | 162 | def construct_args(self): 163 | if (self.use_args): 164 | return self.args 165 | args = "" 166 | if self.cl_p: 167 | args += 'p' 168 | if self.cl_r: 169 | args += 'r' 170 | if self.cl_q: 171 | args+= 'q%f' % self.cl_q_angle 172 | if self.cl_a: 173 | args += 'a%f' % self.cl_a_value 174 | if self.cl_c: 175 | args += 'c' 176 | if self.cl_D: 177 | args += 'D' 178 | if self.cl_v: 179 | args += 'v' 180 | 181 | args += 'ziQ' 182 | return args 183 | 184 | def draw(self, context): 185 | layout = self.layout 186 | col = layout.column() 187 | 188 | box = col.box() 189 | row = box.row(align=True) 190 | row.prop(self, "use_args", text="Use args") 191 | 192 | if self.use_args: 193 | row = box.row(align=True) 194 | row.prop(self, "args", text="Args") 195 | 196 | layout.separator() 197 | 198 | if not self.use_args: 199 | 200 | box = col.box() 201 | 202 | row = box.row(align=True) 203 | row.prop(self, "cl_q") 204 | row.prop(self, "cl_q_angle", text="") 205 | layout.separator() 206 | 207 | row = box.row(align=True) 208 | row.prop(self, "cl_a") 209 | row.prop(self, "cl_a_value", text="") 210 | layout.separator() 211 | 212 | row = box.row(align=True) 213 | row.prop(self, "cl_c") 214 | row.prop(self, "cl_p") 215 | layout.separator() 216 | 217 | #row = col.row(align=True) 218 | #row.prop(self, "cl_v") 219 | #row.prop(self, "cl_D") 220 | #layout.separator() 221 | 222 | row = box.row(align=True) 223 | row.prop(self, "cl_r") 224 | layout.separator() 225 | 226 | 227 | def execute(self, ctx): 228 | args = self.construct_args() 229 | objs = bpy.context.selected_objects 230 | for o in objs: 231 | (verts, faces, N) = triangulate_object(o, args) 232 | obj = add_mesh(verts, faces, o.data.name + '_triangulated', o.name + '_triangulate') 233 | 234 | original_verts = [x for x in obj.data.vertices[:N]] 235 | vg = obj.vertex_groups.new(name="Triangle Boundary") 236 | vg.add(range(N), 1.0, 'ADD') 237 | for v in original_verts: 238 | v.select = True 239 | 240 | obj.matrix_world = o.matrix_world 241 | ctx.scene.objects.link(obj) 242 | 243 | return {'FINISHED'} 244 | 245 | class TrianglePanel(bpy.types.Panel): 246 | """Creates a Panel in the Object properties window""" 247 | bl_space_type = 'VIEW_3D' 248 | bl_region_type = 'TOOLS' 249 | bl_category = 'Tools' 250 | bl_context = "objectmode" 251 | bl_label = "Triangle" 252 | 253 | def draw(self, context): 254 | layout = self.layout 255 | row = layout.row() 256 | row = layout.row() 257 | row.operator("object.triangulate", text='Triangulate') 258 | 259 | 260 | def menu_func(self, context): 261 | self.layout.operator(Triangulate.bl_idname, icon='MESH_CUBE') 262 | 263 | def register(): 264 | bpy.utils.register_class(Triangulate) 265 | bpy.utils.register_class(TrianglePanel) 266 | 267 | def unregister(): 268 | bpy.utils.unregister_class(TrianglePanel) 269 | bpy.utils.unregister_class(Triangulate) 270 | 271 | if __name__ == "__main__": 272 | register() 273 | --------------------------------------------------------------------------------