⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀         ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

298 lines
11 KiB

  1. #!BPY
  2. bl_info = {
  3. "name": "Solidify Wireframe",
  4. "author": "Yorik van Havre, Alejandro Sierra, Howard Trickey",
  5. "description": "Turns the selected edges of a mesh into solid geometry",
  6. "version": (2, 3),
  7. "blender": (2, 5, 8),
  8. "category": "Mesh",
  9. "location": "Mesh > Solidify Wireframe",
  10. "warning": '',
  11. "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/Scripts/Modeling/Solidify_Wireframe",
  12. "tracker_url": "http://projects.blender.org/tracker/?func=detail&group_id=153&aid=26997&atid=467",
  13. }
  14. # ***** BEGIN GPL LICENSE BLOCK *****
  15. #
  16. # This program is free software; you can redistribute it and/or
  17. # modify it under the terms of the GNU General Public License
  18. # as published by the Free Software Foundation; either version 2
  19. # of the License, or (at your option) any later version.
  20. #
  21. # This program is distributed in the hope that it will be useful,
  22. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  23. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See th
  24. # GNU General Public License for more details.
  25. #
  26. # You should have received a copy of the GNU General Public License
  27. # along with this program; if not, write to the Free Software Foundation,
  28. # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  29. #
  30. # ***** END GPL LICENCE BLOCK *****
  31. import bpy, mathutils
  32. cube_faces = [ [0,3,2,1], [5,6,7,4], [0,1,5,4],
  33. [7,6,2,3], [2,6,5,1], [0,4,7,3] ]
  34. cube_normals = [ mathutils.Vector((0,0,-1)),
  35. mathutils.Vector((0,0,1)),
  36. mathutils.Vector((0,-1,0)),
  37. mathutils.Vector((0,1,0)),
  38. mathutils.Vector((1,0,0)),
  39. mathutils.Vector((-1,0,0)) ]
  40. def create_cube(me, v, d):
  41. x = v.co.x
  42. y = v.co.y
  43. z = v.co.z
  44. coords=[ [x-d,y-d,z-d], [x+d,y-d,z-d], [x+d,y+d,z-d], [x-d,y+d,z-d],
  45. [x-d,y-d,z+d], [x+d,y-d,z+d], [x+d,y+d,z+d], [x-d,y+d,z+d] ]
  46. for coord in coords:
  47. me.vertices.add(1)
  48. me.vertices[-1].co = mathutils.Vector(coord)
  49. def norm_dot(e, k, fnorm, me):
  50. v = me.vertices[e[1]].co - me.vertices[e[0]].co
  51. if k == 1:
  52. v = -v
  53. v.normalize()
  54. return v * fnorm
  55. def fill_cube_face(me, index, f):
  56. return [index + cube_faces[f][i] for i in range(4)]
  57. # Coords of jth point of face f in cube instance i
  58. def cube_face_v(me, f, i, j):
  59. return me.vertices[i + cube_faces[f][j]].co
  60. def cube_face_center(me, f, i):
  61. return 0.5 * (cube_face_v(me, f, i, 0) + \
  62. cube_face_v(me, f, i, 2))
  63. # Return distance between points on two faces when
  64. # each point is projected onto the plane that goes through
  65. # the face center and is perpendicular to the line
  66. # through the face centers.
  67. def projected_dist(me, i1, i2, f1, f2, j1, j2):
  68. f1center = cube_face_center(me, f1, i1)
  69. f2center = cube_face_center(me, f2, i2)
  70. axis_norm = (f2center - f1center).normalized()
  71. v1 = cube_face_v(me, f1, i1, j1)
  72. v2 = cube_face_v(me, f2, i2, j2)
  73. v1proj = v1 - (axis_norm * (v1 - f1center)) * axis_norm
  74. v2proj = v2 - (axis_norm * (v2 - f2center)) * axis_norm
  75. return (v2proj - v1proj).length
  76. def skin_edges(me, i1, i2, f1, f2):
  77. # Connect verts starting at i1 forming cube face f1
  78. # to those starting at i2 forming cube face f2.
  79. # Need to find best alignment to avoid a twist.
  80. shortest_length = 1e6
  81. f2_start_index = 0
  82. for i in range(4):
  83. x = projected_dist(me, i1, i2, f1, f2, 0, i)
  84. if x < shortest_length:
  85. shortest_length = x
  86. f2_start_index = i
  87. ans = []
  88. j = f2_start_index
  89. for i in range(4):
  90. fdata = [i1 + cube_faces[f1][i],
  91. i2 + cube_faces[f2][j],
  92. i2 + cube_faces[f2][(j + 1) % 4],
  93. i1 + cube_faces[f1][(i - 1) % 4]]
  94. if fdata[3] == 0:
  95. fdata = [fdata[3]] + fdata[0:3]
  96. ans.extend(fdata)
  97. j = (j - 1) % 4
  98. return ans
  99. # Return map: v -> list of length len(node_normals) where
  100. # each element of the list is either None (no assignment)
  101. # or ((v0, v1), 0 or 1) giving an edge and direction that face is assigned to.
  102. def find_assignment(me, edges, vert_edges, node_normals):
  103. nf = len(node_normals)
  104. feasible = {}
  105. for e in edges:
  106. for k in (0, 1):
  107. fds = [(f, norm_dot(e, k, node_normals[f], me)) for f in range(nf)]
  108. feasible[(e, k)] = [fd for fd in fds if fd[1] > 0.01]
  109. assignment = {}
  110. for v, ves in vert_edges.items():
  111. assignment[v] = best_assignment(ves, feasible, nf)
  112. return assignment
  113. def best_assignment(ves, feasible, nf):
  114. apartial = [ None ] * nf
  115. return best_assign_help(ves, feasible, apartial, 0.0)[0]
  116. def best_assign_help(ves, feasible, apartial, sumpartial):
  117. if len(ves) == 0:
  118. return (apartial, sumpartial)
  119. else:
  120. ek0 = ves[0]
  121. vesrest = ves[1:]
  122. feas = feasible[ek0]
  123. bestsum = 0
  124. besta = None
  125. for (f, d) in feas:
  126. if apartial[f] is None:
  127. ap = apartial[:]
  128. ap[f] = ek0
  129. # sum up d**2 to penalize smaller d's more
  130. sp = sumpartial + d*d
  131. (a, s) = best_assign_help(vesrest, feasible, ap, sp)
  132. if s > bestsum:
  133. bestsum = s
  134. besta = a
  135. if besta:
  136. return (besta, bestsum)
  137. else:
  138. # not feasible to assign e0, k0; try to assign rest
  139. return best_assign_help(vesrest, feasible, apartial, sumpartial)
  140. def assigned_face(e, assignment):
  141. (v0, v1), dir = e
  142. a = assignment[v1]
  143. for j, ee in enumerate(a):
  144. if e == ee:
  145. return j
  146. return -1
  147. def create_wired_mesh(me2, me, thick):
  148. edges = []
  149. vert_edges = {}
  150. for be in me.edges:
  151. if be.select and not be.hide:
  152. e = (be.key[0], be.key[1])
  153. edges.append(e)
  154. for k in (0, 1):
  155. if e[k] not in vert_edges:
  156. vert_edges[e[k]] = []
  157. vert_edges[e[k]].append((e, k))
  158. assignment = find_assignment(me, edges, vert_edges, cube_normals)
  159. # Create the geometry
  160. n_idx = {}
  161. for v in assignment:
  162. vpos = me.vertices[v]
  163. index = len(me2.vertices)
  164. # We need to associate each node with the new geometry
  165. n_idx[v] = index
  166. # Geometry for the nodes, each one a cube
  167. create_cube(me2, vpos, thick)
  168. # Skin using the new geometry
  169. cfaces = []
  170. for k, f in assignment.items():
  171. # Skin the nodes
  172. for i in range(len(cube_faces)):
  173. if f[i] is None:
  174. cfaces.extend(fill_cube_face(me2, n_idx[k], i))
  175. else:
  176. (v0, v1), dir = f[i]
  177. # only skin between edges in forward direction
  178. # to avoid making doubles
  179. if dir == 1:
  180. # but first make sure other end actually assigned
  181. i2 = assigned_face(((v0, v1), 0), assignment)
  182. if i2 == -1:
  183. cfaces.extend(fill_cube_face(me2, n_idx[k], i))
  184. continue
  185. i2 = assigned_face(((v0, v1), 1), assignment)
  186. if i2 != -1:
  187. cfaces.extend(skin_edges(me2, n_idx[v0], n_idx[v1], i, i2))
  188. else:
  189. # assignment failed for this edge
  190. cfaces.extend(fill_cube_face(me2, n_idx[k], i))
  191. # adding faces to the mesh
  192. me2.faces.add(len(cfaces) // 4)
  193. me2.faces.foreach_set("vertices_raw", cfaces)
  194. me2.update(calc_edges=True)
  195. # panel containing tools
  196. class VIEW3D_PT_tools_SolidifyWireframe(bpy.types.Panel):
  197. bl_space_type = 'VIEW_3D'
  198. bl_region_type = 'TOOLS'
  199. bl_context = "mesh_edit"
  200. bl_label = "Solidify Wireframe"
  201. def draw(self, context):
  202. active_obj = context.active_object
  203. layout = self.layout
  204. col = layout.column(align=True)
  205. col.operator("mesh.solidify_wireframe", text="Solidify")
  206. col.prop(context.scene, "swThickness")
  207. col.prop(context.scene, "swSelectNew")
  208. # a class for your operator
  209. class SolidifyWireframe(bpy.types.Operator):
  210. '''Turns the selected edges of a mesh into solid objects'''
  211. bl_idname = "mesh.solidify_wireframe"
  212. bl_label = "Solidify Wireframe"
  213. bl_options = {'REGISTER', 'UNDO'}
  214. def invoke(self, context, event):
  215. return self.execute(context)
  216. @classmethod
  217. def poll(cls, context):
  218. ob = context.active_object
  219. return ob and ob.type == 'MESH'
  220. def execute(self, context):
  221. # Get the active object
  222. ob_act = context.active_object
  223. # getting current edit mode
  224. currMode = ob_act.mode
  225. # switching to object mode
  226. bpy.ops.object.mode_set(mode='OBJECT')
  227. bpy.ops.object.select_all(action='DESELECT')
  228. # getting mesh data
  229. mymesh = ob_act.data
  230. #getting new mesh
  231. newmesh = bpy.data.meshes.new(mymesh.name + " wire")
  232. obj = bpy.data.objects.new(newmesh.name,newmesh)
  233. obj.location = ob_act.location
  234. obj.rotation_euler = ob_act.rotation_euler
  235. obj.scale = ob_act.scale
  236. context.scene.objects.link(obj)
  237. create_wired_mesh(newmesh, mymesh, context.scene.swThickness)
  238. # restoring original editmode if needed
  239. if context.scene.swSelectNew:
  240. obj.select = True
  241. context.scene.objects.active = obj
  242. else:
  243. bpy.ops.object.mode_set(mode=currMode)
  244. # returning after everything is done
  245. return {'FINISHED'}
  246. # Register the operator
  247. def solidifyWireframe_menu_func(self, context):
  248. self.layout.operator(SolidifyWireframe.bl_idname, text="Solidify Wireframe", icon='PLUGIN')
  249. # Add "Solidify Wireframe" menu to the "Mesh" menu.
  250. def register():
  251. bpy.utils.register_module(__name__)
  252. bpy.types.Scene.swThickness = bpy.props.FloatProperty(name="Thickness",
  253. description="Thickness of the skinned edges",
  254. default=0.02)
  255. bpy.types.Scene.swSelectNew = bpy.props.BoolProperty(name="Select wire",
  256. description="If checked, the wire object will be selected after creation",
  257. default=True)
  258. bpy.types.VIEW3D_MT_edit_mesh_edges.append(solidifyWireframe_menu_func)
  259. # Remove "Solidify Wireframe" menu entry from the "Mesh" menu.
  260. def unregister():
  261. bpy.utils.register_module(__name__)
  262. del bpy.types.Scene.swThickness
  263. bpy.types.VIEW3D_MT_edit_mesh_edges.remove(solidifyWireframe_menu_func)
  264. if __name__ == "__main__":
  265. register()