Fix T52833: OBJ triangulate doesn't match viewport
[blender-addons.git] / add_advanced_objects_menu / copy2.py
blob489f6deeb339704322410f051f5f8c90c55e56e5
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 #####
20 bl_info = {
21 "name": "Copy2 Vertices, Edges or Faces",
22 "author": "Eleanor Howick (elfnor.com)",
23 "version": (0, 1, 1),
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",
27 "warning": "",
28 "category": "Object"
31 import bpy
32 from bpy.types import Operator
33 from bpy.props import (
34 BoolProperty,
35 EnumProperty,
36 FloatProperty,
38 from mathutils import (
39 Vector,
40 Matrix,
44 class Copy2(Operator):
45 bl_idname = "mesh.copy2"
46 bl_label = "Copy 2"
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"}
51 obj_list = None
53 def obj_list_cb(self, context):
54 return Copy2.obj_list
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")]
68 return sec_list
70 copytype = EnumProperty(
71 items=(('V', "Vertex",
72 "Paste the Copied Geometry to Vertices of the Active Object", 'VERTEXSEL', 0),
73 ('E', "Edge",
74 "Paste the Copied Geometry to Edges of the Active Object", 'EDGESEL', 1),
75 ('F', "Face",
76 "Paste the Copied Geometry to Faces of the Active Object", 'FACESEL', 2)),
78 copyfromobject = EnumProperty(
79 name="Copy from",
80 description="Copy an Object from the list",
81 items=obj_list_cb
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",
91 default=False
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(
99 name="Scale",
100 default=1.0,
101 min=0.0,
104 @classmethod
105 def poll(cls, context):
106 obj = context.active_object
107 return obj and obj.type == "MESH"
109 def draw(self, context):
110 layout = self.layout
112 layout.prop(self, "copyfromobject")
113 layout.label("to:")
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")
121 if self.edgescale:
122 layout.prop(self, "scale")
123 return
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")
139 return {"CANCELLED"}
140 try:
141 copy_to_from(
142 context.scene,
143 copy_to_object,
144 copy_from_object,
145 self.copytype,
146 axes,
147 self.edgescale,
148 self.scale
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))
155 return {"CANCELLED"}
157 return {"FINISHED"}
159 def invoke(self, context, event):
160 Copy2.obj_list = [(obj.name, obj.name, obj.name) for obj in bpy.data.objects]
162 return {"FINISHED"}
165 def copy_to_from(scene, to_obj, from_obj, copymode, axes, edgescale, scale):
166 if copymode == 'V':
167 vertex_copy(scene, to_obj, from_obj, axes)
169 if copymode == 'E':
170 # don't pass edgescalling to object types that cannot be scaled
171 if from_obj.type in ["CAMERA", "LAMP", "EMPTY", "ARMATURE", "SPEAKER", "META"]:
172 edgescale = False
173 edge_copy(scene, to_obj, from_obj, axes, edgescale, scale)
175 if copymode == 'F':
176 face_copy(scene, to_obj, from_obj, axes)
179 axes_dict = {'XY': (1, 2, 0),
180 'XZ': (2, 1, 0),
181 'YX': (0, 2, 1),
182 'YZ': (2, 0, 1),
183 'ZX': (0, 1, 2),
184 'ZY': (1, 0, 2)}
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()
197 # rotation first
198 z_axis = zdir
199 x_axis = xdir
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]
203 rot_mat = Matrix()
204 rot_mat[A].xyz = x_axis
205 rot_mat[B].xyz = y_axis
206 rot_mat[C].xyz = z_axis
207 rot_mat.transpose()
209 # rotate object
210 copy_obj.matrix_world = rot_mat
212 # move object into position
213 copy_obj.location = pos
215 # scale object
216 if scale is not None:
217 copy_obj.scale = scale
219 return copy_obj
222 def vertex_copy(scene, obj, source_obj, axes):
223 # vertex select mode
224 sel_verts = []
225 copy_list = []
227 for v in obj.data.vertices:
228 if v.select is True:
229 sel_verts.append(v)
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])
238 for v in sel_verts:
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()
245 edir = pos - xco
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:
257 copy.select = True
258 obj.select = False
261 def edge_copy(scene, obj, source_obj, axes, es, scale):
262 # edge select mode
263 sel_edges = []
264 copy_list = []
266 for e in obj.data.edges:
267 if e.select is True:
268 sel_edges.append(e)
270 for e in sel_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()
274 pos = (v0 + v1) / 2
275 # xdir is along edge
276 xdir = v0 - v1
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
287 zdir = vn0p + vn1p
288 zdir = zdir.normalized()
289 escale = None
290 if es:
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:
300 copy.select = True
301 obj.select = False
304 def face_copy(scene, obj, source_obj, axes):
305 # face select mode
306 sel_faces = []
307 copy_list = []
309 for f in obj.data.polygons:
310 if f.select is True:
311 sel_faces.append(f)
313 for f in sel_faces:
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
319 fn = fn.normalized()
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:
326 copy.select = True
327 obj.select = False
330 def register():
331 bpy.utils.register_class(Copy2)
334 def unregister():
335 bpy.utils.unregister_class(Copy2)
338 if __name__ == "__main__":
339 register()