1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation, either version 3 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see http://www.gnu.org/licenses/
15 # or write to the Free Software Foundation, Inc., 51 Franklin Street,
16 # Fifth Floor, Boston, MA 02110-1301, USA.
18 # ##### END GPL LICENSE BLOCK #####
21 "name": "Copy2 Vertices, Edges or Faces",
22 "author": "Eleanor Howick (elfnor.com)",
24 "blender": (2, 71, 0),
25 "location": "3D View > Object > Copy 2",
26 "description": "Copy one object to the selected vertices, edges or faces of another object",
32 from bpy
.types
import Operator
33 from bpy
.props
import (
38 from mathutils
import (
44 class Copy2(Operator
):
45 bl_idname
= "mesh.copy2"
47 bl_description
= ("Copy Vertices, Edges or Faces to the Selected object\n"
48 "Needs an existing Active Mesh Object")
49 bl_options
= {"REGISTER", "UNDO"}
53 def obj_list_cb(self
, context
):
56 def sec_axes_list_cb(self
, context
):
57 if self
.priaxes
== 'X':
58 sec_list
= [('Y', "Y", "Secondary axis Y"),
59 ('Z', "Z", "Secondary axis Z")]
61 if self
.priaxes
== 'Y':
62 sec_list
= [('X', "X", "Secondary axis X"),
63 ('Z', "Z", "Secondary axis Z")]
65 if self
.priaxes
== 'Z':
66 sec_list
= [('X', "X", "Secondary axis X"),
67 ('Y', "Y", "Secondary axis Y")]
70 copytype
= EnumProperty(
71 items
=(('V', "Vertex",
72 "Paste the Copied Geometry to Vertices of the Active Object", 'VERTEXSEL', 0),
74 "Paste the Copied Geometry to Edges of the Active Object", 'EDGESEL', 1),
76 "Paste the Copied Geometry to Faces of the Active Object", 'FACESEL', 2)),
78 copyfromobject
= EnumProperty(
80 description
="Copy an Object from the list",
83 priaxes
= EnumProperty(
84 description
="Primary axes used for Copied Object orientation",
85 items
=(('X', "X", "Along X"),
86 ('Y', "Y", "Along Y"),
87 ('Z', "Z", "Along Z")),
89 edgescale
= BoolProperty(
90 name
="Scale to fill edge",
93 secaxes
= EnumProperty(
94 name
="Secondary Axis",
95 description
="Secondary axis used for Copied Object orientation",
96 items
=sec_axes_list_cb
98 scale
= FloatProperty(
105 def poll(cls
, context
):
106 obj
= context
.active_object
107 return obj
and obj
.type == "MESH"
109 def draw(self
, context
):
112 layout
.prop(self
, "copyfromobject")
114 layout
.prop(self
, "copytype", expand
=True)
115 layout
.label("Primary axis:")
116 layout
.prop(self
, "priaxes", expand
=True)
117 layout
.label("Secondary axis:")
118 layout
.prop(self
, "secaxes", expand
=True)
119 if self
.copytype
== "E":
120 layout
.prop(self
, "edgescale")
122 layout
.prop(self
, "scale")
125 def execute(self
, context
):
126 copytoobject
= context
.active_object
.name
127 axes
= self
.priaxes
+ self
.secaxes
129 # check if there is a problem with the strings related to some chars
130 copy_to_object
= bpy
.data
.objects
[copytoobject
] if \
131 copytoobject
in bpy
.data
.objects
else None
133 copy_from_object
= bpy
.data
.objects
[self
.copyfromobject
] if \
134 self
.copyfromobject
in bpy
.data
.objects
else None
136 if copy_to_object
is None or copy_from_object
is None:
137 self
.report({"WARNING"},
138 "There was a problem with retrieving Object data. Operation Cancelled")
150 except Exception as e
:
151 self
.report({"WARNING"},
152 "Copy2 could not be completed (Check the Console for more info)")
153 print("\n[Add Advanced Objects]\nOperator: mesh.copy2\n{}\n".format(e
))
159 def invoke(self
, context
, event
):
160 Copy2
.obj_list
= [(obj
.name
, obj
.name
, obj
.name
) for obj
in bpy
.data
.objects
]
165 def copy_to_from(scene
, to_obj
, from_obj
, copymode
, axes
, edgescale
, scale
):
167 vertex_copy(scene
, to_obj
, from_obj
, axes
)
170 # don't pass edgescalling to object types that cannot be scaled
171 if from_obj
.type in ["CAMERA", "LAMP", "EMPTY", "ARMATURE", "SPEAKER", "META"]:
173 edge_copy(scene
, to_obj
, from_obj
, axes
, edgescale
, scale
)
176 face_copy(scene
, to_obj
, from_obj
, axes
)
179 axes_dict
= {'XY': (1, 2, 0),
187 def copyto(scene
, source_obj
, pos
, xdir
, zdir
, axes
, scale
=None):
189 copy the source_obj to pos, so its primary axis points in zdir and its
190 secondary axis points in xdir
192 copy_obj
= source_obj
.copy()
193 scene
.objects
.link(copy_obj
)
195 xdir
= xdir
.normalized()
196 zdir
= zdir
.normalized()
200 y_axis
= z_axis
.cross(x_axis
)
201 # use axes_dict to assign the axis as chosen in panel
202 A
, B
, C
= axes_dict
[axes
]
204 rot_mat
[A
].xyz
= x_axis
205 rot_mat
[B
].xyz
= y_axis
206 rot_mat
[C
].xyz
= z_axis
210 copy_obj
.matrix_world
= rot_mat
212 # move object into position
213 copy_obj
.location
= pos
216 if scale
is not None:
217 copy_obj
.scale
= scale
222 def vertex_copy(scene
, obj
, source_obj
, axes
):
227 for v
in obj
.data
.vertices
:
231 # make a set for each vertex. The set contains all the connected vertices
232 # use sets so the list is unique
233 vert_con
= [set() for i
in range(len(obj
.data
.vertices
))]
234 for e
in obj
.data
.edges
:
235 vert_con
[e
.vertices
[0]].add(e
.vertices
[1])
236 vert_con
[e
.vertices
[1]].add(e
.vertices
[0])
239 pos
= v
.co
* obj
.matrix_world
.transposed()
240 xco
= obj
.data
.vertices
[list(vert_con
[v
.index
])[0]].co
* obj
.matrix_world
.transposed()
242 zdir
= (v
.co
+ v
.normal
) * obj
.matrix_world
.transposed() - pos
243 zdir
= zdir
.normalized()
247 # edir is nor perpendicular to z dir
248 # want xdir to be projection of edir onto plane through pos with direction zdir
249 xdir
= edir
- edir
.dot(zdir
) * zdir
250 xdir
= -xdir
.normalized()
252 copy
= copyto(scene
, source_obj
, pos
, xdir
, zdir
, axes
)
253 copy_list
.append(copy
)
255 # select all copied objects
256 for copy
in copy_list
:
261 def edge_copy(scene
, obj
, source_obj
, axes
, es
, scale
):
266 for e
in obj
.data
.edges
:
271 # pos is average of two edge vertexs
272 v0
= obj
.data
.vertices
[e
.vertices
[0]].co
* obj
.matrix_world
.transposed()
273 v1
= obj
.data
.vertices
[e
.vertices
[1]].co
* obj
.matrix_world
.transposed()
277 xlen
= xdir
.magnitude
278 xdir
= xdir
.normalized()
279 # project each edge vertex normal onto plane normal to xdir
280 vn0
= (obj
.data
.vertices
[e
.vertices
[0]].co
* obj
.matrix_world
.transposed() +
281 obj
.data
.vertices
[e
.vertices
[0]].normal
) - v0
282 vn1
= (obj
.data
.vertices
[e
.vertices
[1]].co
* obj
.matrix_world
.transposed() +
283 obj
.data
.vertices
[e
.vertices
[1]].normal
) - v1
284 vn0p
= vn0
- vn0
.dot(xdir
) * xdir
285 vn1p
= vn1
- vn1
.dot(xdir
) * xdir
286 # the mean of the two projected normals is the zdir
288 zdir
= zdir
.normalized()
291 escale
= Vector([1.0, 1.0, 1.0])
292 i
= list('XYZ').index(axes
[1])
293 escale
[i
] = scale
* xlen
/ source_obj
.dimensions
[i
]
295 copy
= copyto(scene
, source_obj
, pos
, xdir
, zdir
, axes
, scale
=escale
)
296 copy_list
.append(copy
)
298 # select all copied objects
299 for copy
in copy_list
:
304 def face_copy(scene
, obj
, source_obj
, axes
):
309 for f
in obj
.data
.polygons
:
314 fco
= f
.center
* obj
.matrix_world
.transposed()
315 # get first vertex corner of transformed object
316 vco
= obj
.data
.vertices
[f
.vertices
[0]].co
* obj
.matrix_world
.transposed()
317 # get face normal of transformed object
318 fn
= (f
.center
+ f
.normal
) * obj
.matrix_world
.transposed() - fco
321 copy
= copyto(scene
, source_obj
, fco
, vco
- fco
, fn
, axes
)
322 copy_list
.append(copy
)
324 # select all copied objects
325 for copy
in copy_list
:
331 bpy
.utils
.register_class(Copy2
)
335 bpy
.utils
.unregister_class(Copy2
)
338 if __name__
== "__main__":