Page Menu
Home
Search
Configure Global Search
Log In
Files
F18254
mesh_remirror.py
Public
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Authored By
Philip Lafleur (bknsz)
Nov 13 2013, 4:07 PM
Size
9 KB
Subscribers
Henrik Berglund (cyaoeu)
mesh_remirror.py
View Options
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# The Original Code is Copyright (C) 2012 by Philip Lafleur.
# All rights reserved.
#
# Contact: bksnzq {at} gmail {dot} com
#
# The Original Code is: all of this file.
#
# Contributor(s): none yet.
#
# ***** END GPL LICENSE BLOCK *****
bl_info
=
{
"name"
:
"Remirror"
,
"author"
:
"Philip Lafleur"
,
"version"
:
(
0
,
9
),
"blender"
:
(
2
,
6
,
3
),
"location"
:
"View3D > Object > Mirror > Remirror"
,
"description"
:
"Non-destructively update symmetry of a mirrored mesh"
,
"warning"
:
""
,
"wiki_url"
:
(
"http://wiki.blender.org/index.php/Extensions:2.6"
"/Py/Scripts/Mesh/Remirror"
),
"tracker_url"
:
(
"http://projects.blender.org/tracker/index.php?"
"func=detail&aid=32166&group_id=153&atid=467"
),
"category"
:
"Mesh"
}
import
bpy
import
bmesh
ERR_ASYMMETRY
=
"Asymmetry encountered (central edge loop(s) not centered?)"
ERR_BAD_PATH
=
"Couldn't follow edge path (inconsistent normals?)"
ERR_CENTRAL_LOOP
=
"Failed to find central edge loop(s). Please recenter."
ERR_FACE_COUNT
=
"Encountered edge with more than 2 faces attached."
CENTRAL_LOOP_MARGIN
=
1e-5
class
Remirror
(
bpy
.
types
.
Operator
):
bl_idname
=
"mesh.remirror"
bl_label
=
"Remirror"
bl_description
=
"Non-destructively update symmetry of a mirrored mesh"
bl_options
=
{
'REGISTER'
,
'UNDO'
}
axis
=
bpy
.
props
.
EnumProperty
(
name
=
"Axis"
,
description
=
"Mirror axis"
,
items
=
((
'X'
,
"X"
,
"X Axis"
),
(
'Y'
,
"Y"
,
"Y Axis"
),
(
'Z'
,
"Z"
,
"Z Axis"
)))
source
=
bpy
.
props
.
EnumProperty
(
name
=
"Source"
,
description
=
"Half of mesh to be mirrored on the other half"
,
items
=
((
'POSITIVE'
,
"Positive side"
,
"Positive side"
),
(
'NEGATIVE'
,
"Negative side"
,
"Negative side"
)))
@classmethod
def
poll
(
cls
,
context
):
obj
=
context
.
active_object
return
(
obj
and
obj
.
type
==
'MESH'
and
context
.
mode
==
'OBJECT'
)
def
execute
(
self
,
context
):
mesh
=
bpy
.
context
.
active_object
.
data
try
:
remirror
(
mesh
,
{
'X'
:
0
,
'Y'
:
1
,
'Z'
:
2
}[
self
.
axis
],
self
.
source
)
except
ValueError
as
e
:
self
.
report
({
'ERROR'
},
str
(
e
))
return
{
'FINISHED'
}
def
nextEdgeCCW
(
v
,
e_prev
):
"""
Return the edge following e_prev in counter-clockwise order around vertex v
by following the winding of the surrounding faces.
"""
if
len
(
e_prev
.
link_loops
)
==
2
:
# Assumes continuous normals
if
e_prev
.
link_loops
[
0
]
.
vert
is
v
:
return
e_prev
.
link_loops
[
0
]
.
link_loop_prev
.
edge
return
e_prev
.
link_loops
[
1
]
.
link_loop_prev
.
edge
elif
len
(
e_prev
.
link_loops
)
==
1
:
# Assumes only two single-loop edges per vertex
if
e_prev
.
link_loops
[
0
]
.
vert
is
v
:
return
e_prev
.
link_loops
[
0
]
.
link_loop_prev
.
edge
for
edge
in
v
.
link_edges
:
if
len
(
edge
.
link_loops
)
==
1
and
edge
is
not
e_prev
:
return
edge
else
:
raise
ValueError
(
ERR_FACE_COUNT
)
def
nextEdgeCW
(
v
,
e_prev
):
"""
Return the edge following e_prev in clockwise order around vertex v
by following the winding of the surrounding faces.
"""
if
len
(
e_prev
.
link_loops
)
==
2
:
# Assumes continuous normals
if
e_prev
.
link_loops
[
0
]
.
vert
is
not
v
:
return
e_prev
.
link_loops
[
0
]
.
link_loop_next
.
edge
return
e_prev
.
link_loops
[
1
]
.
link_loop_next
.
edge
elif
len
(
e_prev
.
link_loops
)
==
1
:
# Assumes only two single-loop edges per vertex
if
e_prev
.
link_loops
[
0
]
.
vert
is
not
v
:
return
e_prev
.
link_loops
[
0
]
.
link_loop_next
.
edge
for
edge
in
v
.
link_edges
:
if
len
(
edge
.
link_loops
)
==
1
and
edge
is
not
e_prev
:
return
edge
else
:
raise
ValueError
(
ERR_FACE_COUNT
)
def
visitMirrorVerts
(
v_start
,
e_start
,
visitor
):
"""
Call visitor(v_right, v_left) for each pair of mirrored vertices that
are reachable by following a path from v_start along connected edges
without intersecting the central edge loop(s) or any previously-visited
vertices.
v_start: a vertex on a central edge loop
e_start: an edge on a central edge loop such that the next edge in
counter-clockwise order around v_start is on the positive side
of the central loop
"""
er
=
e_start
el
=
e_start
vr
=
v_start
vl
=
v_start
path
=
[(
er
,
el
)]
while
path
:
er
=
nextEdgeCCW
(
vr
,
er
)
el
=
nextEdgeCW
(
vl
,
el
)
if
er
is
path
[
-
1
][
0
]
or
er
.
tag
:
if
not
(
el
is
path
[
-
1
][
1
]
or
el
.
tag
):
raise
ValueError
(
ERR_ASYMMETRY
)
er
=
path
[
-
1
][
0
]
el
=
path
[
-
1
][
1
]
vr
=
er
.
other_vert
(
vr
)
vl
=
el
.
other_vert
(
vl
)
path
.
pop
()
continue
if
el
is
path
[
-
1
][
1
]
or
el
.
tag
:
raise
ValueError
(
ERR_ASYMMETRY
)
vr
=
er
.
other_vert
(
vr
)
vl
=
el
.
other_vert
(
vl
)
if
vr
is
None
:
raise
ValueError
(
ERR_BAD_PATH
)
if
vr
.
tag
:
if
vl
is
None
or
not
vl
.
tag
:
raise
ValueError
(
ERR_ASYMMETRY
)
vr
=
er
.
other_vert
(
vr
)
vl
=
el
.
other_vert
(
vl
)
continue
if
vl
is
None
or
vl
.
tag
:
raise
ValueError
(
ERR_ASYMMETRY
)
path
.
append
((
er
,
el
))
visitor
(
vr
,
vl
)
vr
.
tag
=
True
vl
.
tag
=
True
def
updateVerts
(
v_start
,
e_start
,
axis
,
source
):
def
updatePositive
(
v_right
,
v_left
):
v_left
.
co
=
v_right
.
co
v_left
.
co
[
axis
]
=
-
v_right
.
co
[
axis
]
def
updateNegative
(
v_right
,
v_left
):
v_right
.
co
=
v_left
.
co
v_right
.
co
[
axis
]
=
-
v_left
.
co
[
axis
]
visitMirrorVerts
(
v_start
,
e_start
,
updatePositive
if
source
==
'POSITIVE'
else
updateNegative
)
def
tagCentralEdgePath
(
v
,
e
):
"""
Tag each edge along the path starting at edge e in the direction of vertex v
such that the path evenly divides the number of edges connected to each
vertex.
"""
while
True
:
e
.
tag
=
True
if
len
(
v
.
link_edges
)
%
2
:
if
len
(
v
.
link_faces
)
==
len
(
v
.
link_edges
):
raise
ValueError
(
ERR_CENTRAL_LOOP
)
else
:
return
for
i
in
range
(
len
(
v
.
link_edges
)
//
2
):
e
=
nextEdgeCCW
(
v
,
e
)
v
=
e
.
other_vert
(
v
)
if
v
is
None
:
raise
ValueError
(
ERR_BAD_PATH
)
if
e
.
tag
:
return
def
tagCentralLoops
(
bm
,
axis
):
"""
Attempt to find and tag the edges on the central loop(s) of the bmesh bm
aligned with the given axis.
"""
for
v
in
bm
.
verts
:
v
.
tag
=
False
for
e
in
bm
.
edges
:
e
.
tag
=
False
verts
=
[]
edges
=
[]
for
v
in
bm
.
verts
:
if
(
v
.
co
[
axis
]
<
CENTRAL_LOOP_MARGIN
and
v
.
co
[
axis
]
>
-
CENTRAL_LOOP_MARGIN
):
v
.
tag
=
True
verts
.
append
(
v
)
for
v
in
verts
:
for
e
in
v
.
link_edges
:
if
e
.
other_vert
(
v
)
.
tag
:
e
.
tag
=
True
edges
.
append
(
e
)
for
v
in
verts
:
v
.
tag
=
False
if
not
(
edges
and
verts
):
raise
ValueError
(
ERR_CENTRAL_LOOP
)
for
e
in
edges
:
tagCentralEdgePath
(
e
.
verts
[
0
],
e
)
tagCentralEdgePath
(
e
.
verts
[
1
],
e
)
def
startingVertex
(
edge
,
axis
):
"""
Return the endpoint of the given edge such that the next edge in
counter-clockwise order around the endpoint is on the positive side of
the given axis.
"""
if
len
(
edge
.
link_loops
)
!=
2
:
raise
ValueError
(
ERR_FACE_COUNT
)
loops
=
sorted
(
edge
.
link_loops
,
key
=
lambda
loop
:
loop
.
face
.
calc_center_median
()[
axis
])
return
loops
[
-
1
]
.
vert
def
remirror
(
mesh
,
axis
,
source
):
bm
=
bmesh
.
new
()
bm
.
from_mesh
(
mesh
)
tagCentralLoops
(
bm
,
axis
)
for
e
in
bm
.
edges
:
if
e
.
tag
:
e
.
verts
[
0
]
.
co
[
axis
]
=
0.
e
.
verts
[
1
]
.
co
[
axis
]
=
0.
e
.
verts
[
0
]
.
tag
=
True
e
.
verts
[
1
]
.
tag
=
True
for
e
in
bm
.
edges
:
if
e
.
tag
:
updateVerts
(
startingVertex
(
e
,
axis
),
e
,
axis
,
source
)
for
v
in
bm
.
verts
:
v
.
tag
=
False
for
e
in
bm
.
edges
:
e
.
tag
=
False
bm
.
to_mesh
(
mesh
)
mesh
.
update
(
calc_tessface
=
True
)
def
menuFunc
(
self
,
context
):
self
.
layout
.
operator
(
Remirror
.
bl_idname
)
def
register
():
bpy
.
utils
.
register_class
(
Remirror
)
bpy
.
types
.
VIEW3D_MT_mirror
.
append
(
menuFunc
)
def
unregister
():
bpy
.
types
.
VIEW3D_MT_mirror
.
remove
(
menuFunc
)
bpy
.
utils
.
unregister_class
(
Remirror
)
if
__name__
==
"__main__"
:
register
()
File Metadata
Details
Mime Type
text/x-python
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
4a/87/16e9b32851cb195e812beb50125c
Event Timeline
Henrik Berglund (cyaoeu)
added a subscriber:
Henrik Berglund (cyaoeu)
.
Mar 17 2016, 3:07 AM
Comment Actions
I really like this, thanks! I just wish you could use it on shape keys as well.
Log In to Comment