1 # gpl author: Giuseppe De Marco [BlenderLab] inspired by NirenYang
4 "name": "Set edges length",
5 "description": "Edges length",
6 "author": "Giuseppe De Marco [BlenderLab] inspired by NirenYang",
9 "location": "Toolbar > Tools > Mesh Tools: set Length(Shit+Alt+E)",
17 from mathutils
import Vector
18 from bpy
.types
import Operator
19 from bpy
.props
import (
25 edge_length_debug
= False
26 _error_message
= "Please select at least one edge to fill select history"
27 _error_message_2
= "Edges with shared vertices are not allowed. Please, use scale instead"
29 # Note : Refactor - removed all the operators apart from LengthSet
30 # and merged the other ones as options of length (lijenstina)
33 def get_edge_vector(edge
):
34 verts
= (edge
.verts
[0].co
, edge
.verts
[1].co
)
35 vector
= verts
[1] - verts
[0]
40 def get_selected(bmesh_obj
, geometry_type
):
41 # geometry type should be edges, verts or faces
44 for i
in getattr(bmesh_obj
, geometry_type
):
47 return tuple(selected
)
50 def get_center_vector(verts
):
51 # verts = [Vector((x,y,z)), Vector((x,y,z))]
53 center_vector
= Vector((((verts
[1][0] + verts
[0][0]) / 2.),
54 ((verts
[1][1] + verts
[0][1]) / 2.),
55 ((verts
[1][2] + verts
[0][2]) / 2.)))
59 class LengthSet(Operator
):
60 bl_idname
= "object.mesh_edge_length_set"
61 bl_label
= "Set edge length"
62 bl_description
= ("Change one selected edge length by a specified target,\n"
63 "existing length and different modes\n"
64 "Note: works only with Edges that not share a vertex")
65 bl_options
= {'REGISTER', 'UNDO'}
67 old_length
= FloatProperty(
68 name
="Original length",
71 set_length_type
= EnumProperty(
74 "Input manually the desired Target Length"),
75 ('existing', "Existing Length",
76 "Use existing geometry Edges' characteristics"),
78 name
="Set Type of Input",
80 target_length
= FloatProperty(
82 description
="Input a value for an Edges Length target",
87 existing_length
= EnumProperty(
90 "Set all to shortest Edge of selection"),
92 "Set all to the longest Edge of selection"),
93 ('average', "Average",
94 "Set all to the average Edge length of selection"),
96 "Set all to the active Edge's one\n"
97 "Needs a selection to be done in Edge Select mode"),
99 name
="Existing length"
103 ('fixed', "Fixed", "Fixed"),
104 ('increment', "Increment", "Increment"),
105 ('decrement', "Decrement", "Decrement"),
109 behaviour
= EnumProperty(
111 ('proportional', "Proportional",
112 "Move vertex locations proportionally to the center of the Edge"),
113 ('clockwise', "Clockwise",
114 "Compute the Edges' vertex locations in a clockwise fashion"),
115 ('unclockwise', "Counterclockwise",
116 "Compute the Edges' vertex locations in a counterclockwise fashion"),
118 name
="Resize behavior"
121 originary_edge_length_dict
= {}
126 def poll(cls
, context
):
127 return (context
.edit_object
and context
.object.type == 'MESH')
129 def check(self
, context
):
132 def draw(self
, context
):
135 layout
.label("Original Active length is: {:.3f}".format(self
.old_length
))
137 layout
.label("Input Mode:")
138 layout
.prop(self
, "set_length_type", expand
=True)
139 if self
.set_length_type
== 'manual':
140 layout
.prop(self
, "target_length")
142 layout
.prop(self
, "existing_length", text
="")
144 layout
.label("Mode:")
145 layout
.prop(self
, "mode", text
="")
147 layout
.label("Resize Behavior:")
148 layout
.prop(self
, "behaviour", text
="")
150 def get_existing_edge_length(self
, bm
):
151 if self
.existing_length
!= "active":
152 if self
.existing_length
== "min":
153 return min(self
.edge_lengths
)
154 if self
.existing_length
== "max":
155 return max(self
.edge_lengths
)
156 elif self
.existing_length
== "average":
157 return sum(self
.edge_lengths
) / float(len(self
.selected_edges
))
159 bm
.edges
.ensure_lookup_table()
160 active_edge_length
= None
162 for elem
in reversed(bm
.select_history
):
163 if isinstance(elem
, bmesh
.types
.BMEdge
):
164 active_edge_length
= elem
.calc_length()
166 return active_edge_length
170 def invoke(self
, context
, event
):
171 wm
= context
.window_managerlength
173 obj
= context
.edit_object
174 bm
= bmesh
.from_edit_mesh(obj
.data
)
176 bpy
.ops
.mesh
.select_mode(type="EDGE")
177 self
.selected_edges
= get_selected(bm
, 'edges')
179 if self
.selected_edges
:
182 for edge
in self
.selected_edges
:
183 vector
= get_edge_vector(edge
)
185 if edge
.verts
[0].index
not in vertex_set
:
186 vertex_set
.append(edge
.verts
[0].index
)
188 self
.report({'ERROR_INVALID_INPUT'}, _error_message_2
)
191 if edge
.verts
[1].index
not in vertex_set
:
192 vertex_set
.append(edge
.verts
[1].index
)
194 self
.report({'ERROR_INVALID_INPUT'}, _error_message_2
)
197 # warning, it's a constant !
198 verts_index
= ''.join((str(edge
.verts
[0].index
), str(edge
.verts
[1].index
)))
199 self
.originary_edge_length_dict
[verts_index
] = vector
200 self
.edge_lengths
.append(vector
.length
)
201 self
.old_length
= vector
.length
203 self
.report({'ERROR'}, _error_message
)
206 if edge_length_debug
:
207 self
.report({'INFO'}, str(self
.originary_edge_length_dict
))
209 if bpy
.context
.scene
.unit_settings
.system
== 'IMPERIAL':
210 # imperial to metric conversion
211 vector
.length
= (0.9144 * vector
.length
) / 3
213 self
.target_length
= vector
.length
215 return wm
.invoke_props_dialog(self
)
217 def execute(self
, context
):
219 bpy
.ops
.mesh
.select_mode(type="EDGE")
220 self
.context
= context
222 obj
= context
.edit_object
223 bm
= bmesh
.from_edit_mesh(obj
.data
)
225 self
.selected_edges
= get_selected(bm
, 'edges')
227 if not self
.selected_edges
:
228 self
.report({'ERROR'}, _error_message
)
231 for edge
in self
.selected_edges
:
232 vector
= get_edge_vector(edge
)
233 # what we should see in original length dialog field
234 self
.old_length
= vector
.length
236 if self
.set_length_type
== 'manual':
237 vector
.length
= abs(self
.target_length
)
239 get_lengths
= self
.get_existing_edge_length(bm
)
240 # check for edit mode
242 self
.report({'WARNING'},
243 "Operation Cancelled. "
244 "Active Edge could not be determined (needs selection in Edit Mode)")
247 vector
.length
= get_lengths
249 if vector
.length
== 0.0:
250 self
.report({'ERROR'}, "Operation cancelled. Target length is set to zero")
253 center_vector
= get_center_vector((edge
.verts
[0].co
, edge
.verts
[1].co
))
255 verts_index
= ''.join((str(edge
.verts
[0].index
), str(edge
.verts
[1].index
)))
257 if edge_length_debug
:
258 self
.report({'INFO'},
259 ' - '.join(('vector ' + str(vector
),
260 'originary_vector ' +
261 str(self
.originary_edge_length_dict
[verts_index
])
263 verts
= (edge
.verts
[0].co
, edge
.verts
[1].co
)
265 if edge_length_debug
:
266 self
.report({'INFO'},
267 '\n edge.verts[0].co ' + str(verts
[0]) +
268 '\n edge.verts[1].co ' + str(verts
[1]) +
269 '\n vector.length' + str(vector
.length
))
271 # the clockwise direction have v1 -> v0, unclockwise v0 -> v1
272 if self
.target_length
>= 0:
273 if self
.behaviour
== 'proportional':
274 edge
.verts
[1].co
= center_vector
+ vector
/ 2
275 edge
.verts
[0].co
= center_vector
- vector
/ 2
277 if self
.mode
== 'decrement':
278 edge
.verts
[0].co
= (center_vector
+ vector
/ 2) - \
279 (self
.originary_edge_length_dict
[verts_index
] / 2)
280 edge
.verts
[1].co
= (center_vector
- vector
/ 2) + \
281 (self
.originary_edge_length_dict
[verts_index
] / 2)
283 elif self
.mode
== 'increment':
284 edge
.verts
[1].co
= (center_vector
+ vector
/ 2) + \
285 self
.originary_edge_length_dict
[verts_index
] / 2
286 edge
.verts
[0].co
= (center_vector
- vector
/ 2) - \
287 self
.originary_edge_length_dict
[verts_index
] / 2
289 elif self
.behaviour
== 'unclockwise':
290 if self
.mode
== 'increment':
292 verts
[0] + (self
.originary_edge_length_dict
[verts_index
] + vector
)
293 elif self
.mode
== 'decrement':
295 verts
[1] - (self
.originary_edge_length_dict
[verts_index
] - vector
)
297 edge
.verts
[1].co
= verts
[0] + vector
301 if self
.mode
== 'increment':
303 verts
[1] - (self
.originary_edge_length_dict
[verts_index
] + vector
)
304 elif self
.mode
== 'decrement':
306 verts
[0] + (self
.originary_edge_length_dict
[verts_index
] - vector
)
308 edge
.verts
[0].co
= verts
[1] - vector
310 if bpy
.context
.scene
.unit_settings
.system
== 'IMPERIAL':
312 # yards to metric conversion
313 vector.length = ( 3. * vector.length ) / 0.9144
314 # metric to yards conversion
315 vector.length = ( 0.9144 * vector.length ) / 3.
317 for mvert
in edge
.verts
:
318 # school time: 0.9144 : 3 = X : mvert
319 mvert
.co
= (0.9144 * mvert
.co
) / 3
321 if edge_length_debug
:
322 self
.report({'INFO'},
323 '\n edge.verts[0].co' + str(verts
[0]) +
324 '\n edge.verts[1].co' + str(verts
[1]) +
325 '\n vector' + str(vector
) + '\n v1 > v0:' + str((verts
[1] >= verts
[0]))
327 bmesh
.update_edit_mesh(obj
.data
, True)
333 bpy
.utils
.register_class(LengthSet
)
337 bpy
.utils
.unregister_class(LengthSet
)
340 if __name__
== "__main__":