1 # SPDX-FileCopyrightText: 2019-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # Author: Giuseppe De Marco [BlenderLab] inspired by NirenYang
8 "name": "Set edges length",
9 "description": "Edges length",
10 "author": "Giuseppe De Marco [BlenderLab] inspired by NirenYang",
12 "blender": (2, 80, 0),
13 "location": "Toolbar > Tools > Mesh Tools: set Length(Shit+Alt+E)",
21 from mathutils
import Vector
22 from bpy
.types
import Operator
23 from bpy
.props
import (
29 edge_length_debug
= False
30 _error_message
= "Please select at least one edge to fill select history"
31 _error_message_2
= "Edges with shared vertices are not allowed. Please, use scale instead"
33 # Note : Refactor - removed all the operators apart from LengthSet
34 # and merged the other ones as options of length (lijenstina)
37 def get_edge_vector(edge
):
38 verts
= (edge
.verts
[0].co
, edge
.verts
[1].co
)
39 vector
= verts
[1] - verts
[0]
44 def get_selected(bmesh_obj
, geometry_type
):
45 # geometry type should be edges, verts or faces
48 for i
in getattr(bmesh_obj
, geometry_type
):
51 return tuple(selected
)
54 def get_center_vector(verts
):
55 # verts = [Vector((x,y,z)), Vector((x,y,z))]
57 center_vector
= Vector((((verts
[1][0] + verts
[0][0]) / 2.),
58 ((verts
[1][1] + verts
[0][1]) / 2.),
59 ((verts
[1][2] + verts
[0][2]) / 2.)))
63 class LengthSet(Operator
):
64 bl_idname
= "object.mesh_edge_length_set"
65 bl_label
= "Set edge length"
66 bl_description
= ("Change one selected edge length by a specified target,\n"
67 "existing length and different modes\n"
68 "Note: works only with Edges that not share a vertex")
69 bl_options
= {'REGISTER', 'UNDO'}
71 old_length
: FloatProperty(
72 name
="Original length",
75 set_length_type
: EnumProperty(
78 "Input manually the desired Target Length"),
79 ('existing', "Existing Length",
80 "Use existing geometry Edges' characteristics"),
82 name
="Set Type of Input",
84 target_length
: FloatProperty(
86 description
="Input a value for an Edges Length target",
91 existing_length
: EnumProperty(
94 "Set all to shortest Edge of selection"),
96 "Set all to the longest Edge of selection"),
97 ('average', "Average",
98 "Set all to the average Edge length of selection"),
100 "Set all to the active Edge's one\n"
101 "Needs a selection to be done in Edge Select mode"),
103 name
="Existing length"
107 ('fixed', "Fixed", "Fixed"),
108 ('increment', "Increment", "Increment"),
109 ('decrement', "Decrement", "Decrement"),
113 behaviour
: EnumProperty(
115 ('proportional', "Proportional",
116 "Move vertex locations proportionally to the center of the Edge"),
117 ('clockwise', "Clockwise",
118 "Compute the Edges' vertex locations in a clockwise fashion"),
119 ('unclockwise', "Counterclockwise",
120 "Compute the Edges' vertex locations in a counterclockwise fashion"),
122 name
="Resize behavior"
125 originary_edge_length_dict
= {}
130 def poll(cls
, context
):
131 return (context
.edit_object
and context
.object.type == 'MESH')
133 def check(self
, context
):
136 def draw(self
, context
):
139 layout
.label(text
="Original Active length is: {:.3f}".format(self
.old_length
))
141 layout
.label(text
="Input Mode:")
142 layout
.prop(self
, "set_length_type", expand
=True)
143 if self
.set_length_type
== 'manual':
144 layout
.prop(self
, "target_length")
146 layout
.prop(self
, "existing_length", text
="")
148 layout
.label(text
="Mode:")
149 layout
.prop(self
, "mode", text
="")
151 layout
.label(text
="Resize Behavior:")
152 layout
.prop(self
, "behaviour", text
="")
154 def get_existing_edge_length(self
, bm
):
155 if self
.existing_length
!= "active":
156 if self
.existing_length
== "min":
157 return min(self
.edge_lengths
)
158 if self
.existing_length
== "max":
159 return max(self
.edge_lengths
)
160 elif self
.existing_length
== "average":
161 return sum(self
.edge_lengths
) / float(len(self
.selected_edges
))
163 bm
.edges
.ensure_lookup_table()
164 active_edge_length
= None
166 for elem
in reversed(bm
.select_history
):
167 if isinstance(elem
, bmesh
.types
.BMEdge
):
168 active_edge_length
= elem
.calc_length()
170 return active_edge_length
174 def invoke(self
, context
, event
):
175 wm
= context
.window_manager
177 obj
= context
.edit_object
178 bm
= bmesh
.from_edit_mesh(obj
.data
)
180 bpy
.ops
.mesh
.select_mode(type="EDGE")
181 self
.selected_edges
= get_selected(bm
, 'edges')
183 if self
.selected_edges
:
186 for edge
in self
.selected_edges
:
187 vector
= get_edge_vector(edge
)
189 if edge
.verts
[0].index
not in vertex_set
:
190 vertex_set
.append(edge
.verts
[0].index
)
192 self
.report({'ERROR_INVALID_INPUT'}, _error_message_2
)
195 if edge
.verts
[1].index
not in vertex_set
:
196 vertex_set
.append(edge
.verts
[1].index
)
198 self
.report({'ERROR_INVALID_INPUT'}, _error_message_2
)
201 # warning, it's a constant !
202 verts_index
= ''.join((str(edge
.verts
[0].index
), str(edge
.verts
[1].index
)))
203 self
.originary_edge_length_dict
[verts_index
] = vector
204 self
.edge_lengths
.append(vector
.length
)
205 self
.old_length
= vector
.length
207 self
.report({'ERROR'}, _error_message
)
210 if edge_length_debug
:
211 self
.report({'INFO'}, str(self
.originary_edge_length_dict
))
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
311 if edge_length_debug
:
312 self
.report({'INFO'},
313 '\n edge.verts[0].co' + str(verts
[0]) +
314 '\n edge.verts[1].co' + str(verts
[1]) +
315 '\n vector' + str(vector
) + '\n v1 > v0:' + str((verts
[1] >= verts
[0]))
317 bmesh
.update_edit_mesh(obj
.data
, loop_triangles
=True)
323 bpy
.utils
.register_class(LengthSet
)
327 bpy
.utils
.unregister_class(LengthSet
)
330 if __name__
== "__main__":