1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Author: Giuseppe De Marco [BlenderLab] inspired by NirenYang
6 "name": "Set edges length",
7 "description": "Edges length",
8 "author": "Giuseppe De Marco [BlenderLab] inspired by NirenYang",
10 "blender": (2, 80, 0),
11 "location": "Toolbar > Tools > Mesh Tools: set Length(Shit+Alt+E)",
19 from mathutils
import Vector
20 from bpy
.types
import Operator
21 from bpy
.props
import (
27 edge_length_debug
= False
28 _error_message
= "Please select at least one edge to fill select history"
29 _error_message_2
= "Edges with shared vertices are not allowed. Please, use scale instead"
31 # Note : Refactor - removed all the operators apart from LengthSet
32 # and merged the other ones as options of length (lijenstina)
35 def get_edge_vector(edge
):
36 verts
= (edge
.verts
[0].co
, edge
.verts
[1].co
)
37 vector
= verts
[1] - verts
[0]
42 def get_selected(bmesh_obj
, geometry_type
):
43 # geometry type should be edges, verts or faces
46 for i
in getattr(bmesh_obj
, geometry_type
):
49 return tuple(selected
)
52 def get_center_vector(verts
):
53 # verts = [Vector((x,y,z)), Vector((x,y,z))]
55 center_vector
= Vector((((verts
[1][0] + verts
[0][0]) / 2.),
56 ((verts
[1][1] + verts
[0][1]) / 2.),
57 ((verts
[1][2] + verts
[0][2]) / 2.)))
61 class LengthSet(Operator
):
62 bl_idname
= "object.mesh_edge_length_set"
63 bl_label
= "Set edge length"
64 bl_description
= ("Change one selected edge length by a specified target,\n"
65 "existing length and different modes\n"
66 "Note: works only with Edges that not share a vertex")
67 bl_options
= {'REGISTER', 'UNDO'}
69 old_length
: FloatProperty(
70 name
="Original length",
73 set_length_type
: EnumProperty(
76 "Input manually the desired Target Length"),
77 ('existing', "Existing Length",
78 "Use existing geometry Edges' characteristics"),
80 name
="Set Type of Input",
82 target_length
: FloatProperty(
84 description
="Input a value for an Edges Length target",
89 existing_length
: EnumProperty(
92 "Set all to shortest Edge of selection"),
94 "Set all to the longest Edge of selection"),
95 ('average', "Average",
96 "Set all to the average Edge length of selection"),
98 "Set all to the active Edge's one\n"
99 "Needs a selection to be done in Edge Select mode"),
101 name
="Existing length"
105 ('fixed', "Fixed", "Fixed"),
106 ('increment', "Increment", "Increment"),
107 ('decrement', "Decrement", "Decrement"),
111 behaviour
: EnumProperty(
113 ('proportional', "Proportional",
114 "Move vertex locations proportionally to the center of the Edge"),
115 ('clockwise', "Clockwise",
116 "Compute the Edges' vertex locations in a clockwise fashion"),
117 ('unclockwise', "Counterclockwise",
118 "Compute the Edges' vertex locations in a counterclockwise fashion"),
120 name
="Resize behavior"
123 originary_edge_length_dict
= {}
128 def poll(cls
, context
):
129 return (context
.edit_object
and context
.object.type == 'MESH')
131 def check(self
, context
):
134 def draw(self
, context
):
137 layout
.label(text
="Original Active length is: {:.3f}".format(self
.old_length
))
139 layout
.label(text
="Input Mode:")
140 layout
.prop(self
, "set_length_type", expand
=True)
141 if self
.set_length_type
== 'manual':
142 layout
.prop(self
, "target_length")
144 layout
.prop(self
, "existing_length", text
="")
146 layout
.label(text
="Mode:")
147 layout
.prop(self
, "mode", text
="")
149 layout
.label(text
="Resize Behavior:")
150 layout
.prop(self
, "behaviour", text
="")
152 def get_existing_edge_length(self
, bm
):
153 if self
.existing_length
!= "active":
154 if self
.existing_length
== "min":
155 return min(self
.edge_lengths
)
156 if self
.existing_length
== "max":
157 return max(self
.edge_lengths
)
158 elif self
.existing_length
== "average":
159 return sum(self
.edge_lengths
) / float(len(self
.selected_edges
))
161 bm
.edges
.ensure_lookup_table()
162 active_edge_length
= None
164 for elem
in reversed(bm
.select_history
):
165 if isinstance(elem
, bmesh
.types
.BMEdge
):
166 active_edge_length
= elem
.calc_length()
168 return active_edge_length
172 def invoke(self
, context
, event
):
173 wm
= context
.window_manager
175 obj
= context
.edit_object
176 bm
= bmesh
.from_edit_mesh(obj
.data
)
178 bpy
.ops
.mesh
.select_mode(type="EDGE")
179 self
.selected_edges
= get_selected(bm
, 'edges')
181 if self
.selected_edges
:
184 for edge
in self
.selected_edges
:
185 vector
= get_edge_vector(edge
)
187 if edge
.verts
[0].index
not in vertex_set
:
188 vertex_set
.append(edge
.verts
[0].index
)
190 self
.report({'ERROR_INVALID_INPUT'}, _error_message_2
)
193 if edge
.verts
[1].index
not in vertex_set
:
194 vertex_set
.append(edge
.verts
[1].index
)
196 self
.report({'ERROR_INVALID_INPUT'}, _error_message_2
)
199 # warning, it's a constant !
200 verts_index
= ''.join((str(edge
.verts
[0].index
), str(edge
.verts
[1].index
)))
201 self
.originary_edge_length_dict
[verts_index
] = vector
202 self
.edge_lengths
.append(vector
.length
)
203 self
.old_length
= vector
.length
205 self
.report({'ERROR'}, _error_message
)
208 if edge_length_debug
:
209 self
.report({'INFO'}, str(self
.originary_edge_length_dict
))
211 self
.target_length
= vector
.length
213 return wm
.invoke_props_dialog(self
)
215 def execute(self
, context
):
217 bpy
.ops
.mesh
.select_mode(type="EDGE")
218 self
.context
= context
220 obj
= context
.edit_object
221 bm
= bmesh
.from_edit_mesh(obj
.data
)
223 self
.selected_edges
= get_selected(bm
, 'edges')
225 if not self
.selected_edges
:
226 self
.report({'ERROR'}, _error_message
)
229 for edge
in self
.selected_edges
:
230 vector
= get_edge_vector(edge
)
231 # what we should see in original length dialog field
232 self
.old_length
= vector
.length
234 if self
.set_length_type
== 'manual':
235 vector
.length
= abs(self
.target_length
)
237 get_lengths
= self
.get_existing_edge_length(bm
)
238 # check for edit mode
240 self
.report({'WARNING'},
241 "Operation Cancelled. "
242 "Active Edge could not be determined (needs selection in Edit Mode)")
245 vector
.length
= get_lengths
247 if vector
.length
== 0.0:
248 self
.report({'ERROR'}, "Operation cancelled. Target length is set to zero")
251 center_vector
= get_center_vector((edge
.verts
[0].co
, edge
.verts
[1].co
))
253 verts_index
= ''.join((str(edge
.verts
[0].index
), str(edge
.verts
[1].index
)))
255 if edge_length_debug
:
256 self
.report({'INFO'},
257 ' - '.join(('vector ' + str(vector
),
258 'originary_vector ' +
259 str(self
.originary_edge_length_dict
[verts_index
])
261 verts
= (edge
.verts
[0].co
, edge
.verts
[1].co
)
263 if edge_length_debug
:
264 self
.report({'INFO'},
265 '\n edge.verts[0].co ' + str(verts
[0]) +
266 '\n edge.verts[1].co ' + str(verts
[1]) +
267 '\n vector.length' + str(vector
.length
))
269 # the clockwise direction have v1 -> v0, unclockwise v0 -> v1
270 if self
.target_length
>= 0:
271 if self
.behaviour
== 'proportional':
272 edge
.verts
[1].co
= center_vector
+ vector
/ 2
273 edge
.verts
[0].co
= center_vector
- vector
/ 2
275 if self
.mode
== 'decrement':
276 edge
.verts
[0].co
= (center_vector
+ vector
/ 2) - \
277 (self
.originary_edge_length_dict
[verts_index
] / 2)
278 edge
.verts
[1].co
= (center_vector
- vector
/ 2) + \
279 (self
.originary_edge_length_dict
[verts_index
] / 2)
281 elif self
.mode
== 'increment':
282 edge
.verts
[1].co
= (center_vector
+ vector
/ 2) + \
283 self
.originary_edge_length_dict
[verts_index
] / 2
284 edge
.verts
[0].co
= (center_vector
- vector
/ 2) - \
285 self
.originary_edge_length_dict
[verts_index
] / 2
287 elif self
.behaviour
== 'unclockwise':
288 if self
.mode
== 'increment':
290 verts
[0] + (self
.originary_edge_length_dict
[verts_index
] + vector
)
291 elif self
.mode
== 'decrement':
293 verts
[1] - (self
.originary_edge_length_dict
[verts_index
] - vector
)
295 edge
.verts
[1].co
= verts
[0] + vector
299 if self
.mode
== 'increment':
301 verts
[1] - (self
.originary_edge_length_dict
[verts_index
] + vector
)
302 elif self
.mode
== 'decrement':
304 verts
[0] + (self
.originary_edge_length_dict
[verts_index
] - vector
)
306 edge
.verts
[0].co
= verts
[1] - vector
309 if edge_length_debug
:
310 self
.report({'INFO'},
311 '\n edge.verts[0].co' + str(verts
[0]) +
312 '\n edge.verts[1].co' + str(verts
[1]) +
313 '\n vector' + str(vector
) + '\n v1 > v0:' + str((verts
[1] >= verts
[0]))
315 bmesh
.update_edit_mesh(obj
.data
, loop_triangles
=True)
321 bpy
.utils
.register_class(LengthSet
)
325 bpy
.utils
.unregister_class(LengthSet
)
328 if __name__
== "__main__":