object_utils.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. # ##### BEGIN GPL LICENSE BLOCK #####
  2. #
  3. # This program is free software; you can redistribute it and/or
  4. # modify it under the terms of the GNU General Public License
  5. # as published by the Free Software Foundation; either version 2
  6. # of the License, or (at your option) any later version.
  7. #
  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.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program; if not, write to the Free Software Foundation,
  15. # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. #
  17. # ##### END GPL LICENSE BLOCK #####
  18. # <pep8-80 compliant>
  19. __all__ = (
  20. "add_object_align_init",
  21. "object_data_add",
  22. "AddObjectHelper",
  23. "object_add_grid_scale",
  24. "object_add_grid_scale_apply_operator",
  25. "object_image_guess",
  26. "world_to_camera_view",
  27. )
  28. import bpy
  29. from bpy.props import (
  30. BoolProperty,
  31. FloatVectorProperty,
  32. EnumProperty,
  33. )
  34. def add_object_align_init(context, operator):
  35. """
  36. Return a matrix using the operator settings and view context.
  37. :arg context: The context to use.
  38. :type context: :class:`bpy.types.Context`
  39. :arg operator: The operator, checked for location and rotation properties.
  40. :type operator: :class:`bpy.types.Operator`
  41. :return: the matrix from the context and settings.
  42. :rtype: :class:`mathutils.Matrix`
  43. """
  44. from mathutils import Matrix, Vector, Euler
  45. properties = operator.properties if operator is not None else None
  46. space_data = context.space_data
  47. if space_data and space_data.type != 'VIEW_3D':
  48. space_data = None
  49. # location
  50. if operator and properties.is_property_set("location"):
  51. location = Matrix.Translation(Vector(properties.location))
  52. else:
  53. location = Matrix.Translation(context.scene.cursor.location)
  54. if operator:
  55. properties.location = location.to_translation()
  56. # rotation
  57. add_align_preference = context.preferences.edit.object_align
  58. if operator:
  59. if not properties.is_property_set("rotation"):
  60. # So one of "align" and "rotation" will be set
  61. properties.align = add_align_preference
  62. if properties.align == 'WORLD':
  63. rotation = properties.rotation.to_matrix().to_4x4()
  64. elif properties.align == 'VIEW':
  65. rotation = space_data.region_3d.view_matrix.to_3x3().inverted()
  66. rotation.resize_4x4()
  67. properties.rotation = rotation.to_euler()
  68. elif properties.align == 'CURSOR':
  69. rotation = context.scene.cursor.matrix
  70. rotation.col[3][0:3] = 0.0, 0.0, 0.0
  71. properties.rotation = rotation.to_euler()
  72. else:
  73. rotation = properties.rotation.to_matrix().to_4x4()
  74. else:
  75. if (add_align_preference == 'VIEW') and space_data:
  76. rotation = space_data.region_3d.view_matrix.to_3x3().inverted()
  77. rotation.resize_4x4()
  78. elif add_align_preference == 'CURSOR':
  79. rotation = context.scene.cursor.rotation_euler.to_matrix().to_4x4()
  80. else:
  81. rotation = Matrix()
  82. return location @ rotation
  83. def object_data_add(context, obdata, operator=None, name=None):
  84. """
  85. Add an object using the view context and preference to initialize the
  86. location, rotation and layer.
  87. :arg context: The context to use.
  88. :type context: :class:`bpy.types.Context`
  89. :arg obdata: the data used for the new object.
  90. :type obdata: valid object data type or None.
  91. :arg operator: The operator, checked for location and rotation properties.
  92. :type operator: :class:`bpy.types.Operator`
  93. :arg name: Optional name
  94. :type name: string
  95. :return: the newly created object in the scene.
  96. :rtype: :class:`bpy.types.Object`
  97. """
  98. scene = context.scene
  99. layer = context.view_layer
  100. layer_collection = context.layer_collection or layer.active_layer_collection
  101. scene_collection = layer_collection.collection
  102. for ob in layer.objects:
  103. ob.select_set(False)
  104. if name is None:
  105. name = "Object" if obdata is None else obdata.name
  106. obj_act = layer.objects.active
  107. obj_new = bpy.data.objects.new(name, obdata)
  108. scene_collection.objects.link(obj_new)
  109. obj_new.select_set(True)
  110. obj_new.matrix_world = add_object_align_init(context, operator)
  111. space_data = context.space_data
  112. if space_data.type == 'VIEW_3D':
  113. if space_data.local_view:
  114. obj_new.local_view_set(space_data, True)
  115. if obj_act and obj_act.mode == 'EDIT' and obj_act.type == obj_new.type:
  116. bpy.ops.mesh.select_all(action='DESELECT')
  117. obj_act.select_set(True)
  118. bpy.ops.object.mode_set(mode='OBJECT')
  119. obj_act.select_set(True)
  120. layer.update() # apply location
  121. # layer.objects.active = obj_new
  122. # Match up UV layers, this is needed so adding an object with UV's
  123. # doesn't create new layers when there happens to be a naming mis-match.
  124. uv_new = obdata.uv_layers.active
  125. if uv_new is not None:
  126. uv_act = obj_act.data.uv_layers.active
  127. if uv_act is not None:
  128. uv_new.name = uv_act.name
  129. bpy.ops.object.join() # join into the active.
  130. if obdata:
  131. bpy.data.meshes.remove(obdata)
  132. bpy.ops.object.mode_set(mode='EDIT')
  133. else:
  134. layer.objects.active = obj_new
  135. if context.preferences.edit.use_enter_edit_mode:
  136. bpy.ops.object.mode_set(mode='EDIT')
  137. return obj_new
  138. class AddObjectHelper:
  139. def align_update_callback(self, _context):
  140. if self.align == 'WORLD':
  141. self.rotation.zero()
  142. align_items = (
  143. ('WORLD', "World", "Align the new object to the world"),
  144. ('VIEW', "View", "Align the new object to the view"),
  145. ('CURSOR', "3D Cursor", "Use the 3D cursor orientation for the new object")
  146. )
  147. align: EnumProperty(
  148. name="Align",
  149. items=align_items,
  150. default='WORLD',
  151. update=align_update_callback,
  152. )
  153. location: FloatVectorProperty(
  154. name="Location",
  155. subtype='TRANSLATION',
  156. )
  157. rotation: FloatVectorProperty(
  158. name="Rotation",
  159. subtype='EULER',
  160. )
  161. @classmethod
  162. def poll(cls, context):
  163. return context.scene.library is None
  164. def object_add_grid_scale(context):
  165. """
  166. Return scale which should be applied on object
  167. data to align it to grid scale
  168. """
  169. space_data = context.space_data
  170. if space_data and space_data.type == 'VIEW_3D':
  171. return space_data.overlay.grid_scale_unit
  172. return 1.0
  173. def object_add_grid_scale_apply_operator(operator, context):
  174. """
  175. Scale an operators distance values by the grid size.
  176. """
  177. grid_scale = object_add_grid_scale(context)
  178. properties = operator.properties
  179. properties_def = properties.bl_rna.properties
  180. for prop_id in properties_def.keys():
  181. if not properties.is_property_set(prop_id):
  182. prop_def = properties_def[prop_id]
  183. if prop_def.unit == 'LENGTH' and prop_def.subtype == 'DISTANCE':
  184. setattr(operator, prop_id,
  185. getattr(operator, prop_id) * grid_scale)
  186. def object_image_guess(obj, bm=None):
  187. """
  188. Return a single image used by the object,
  189. first checking the texture-faces, then the material.
  190. """
  191. # TODO, cycles/nodes materials
  192. me = obj.data
  193. if bm is None:
  194. if obj.mode == 'EDIT':
  195. import bmesh
  196. bm = bmesh.from_edit_mesh(me)
  197. if bm is not None:
  198. tex_layer = bm.faces.layers.tex.active
  199. if tex_layer is not None:
  200. for f in bm.faces:
  201. image = f[tex_layer].image
  202. if image is not None:
  203. return image
  204. else:
  205. tex_layer = me.uv_textures.active
  206. if tex_layer is not None:
  207. for tf in tex_layer.data:
  208. image = tf.image
  209. if image is not None:
  210. return image
  211. for m in obj.data.materials:
  212. if m is not None:
  213. # backwards so topmost are highest priority
  214. for mtex in reversed(m.texture_slots):
  215. if mtex and mtex.use_map_color_diffuse:
  216. texture = mtex.texture
  217. if texture and texture.type == 'IMAGE':
  218. image = texture.image
  219. if image is not None:
  220. return image
  221. return None
  222. def world_to_camera_view(scene, obj, coord):
  223. """
  224. Returns the camera space coords for a 3d point.
  225. (also known as: normalized device coordinates - NDC).
  226. Where (0, 0) is the bottom left and (1, 1)
  227. is the top right of the camera frame.
  228. values outside 0-1 are also supported.
  229. A negative 'z' value means the point is behind the camera.
  230. Takes shift-x/y, lens angle and sensor size into account
  231. as well as perspective/ortho projections.
  232. :arg scene: Scene to use for frame size.
  233. :type scene: :class:`bpy.types.Scene`
  234. :arg obj: Camera object.
  235. :type obj: :class:`bpy.types.Object`
  236. :arg coord: World space location.
  237. :type coord: :class:`mathutils.Vector`
  238. :return: a vector where X and Y map to the view plane and
  239. Z is the depth on the view axis.
  240. :rtype: :class:`mathutils.Vector`
  241. """
  242. from mathutils import Vector
  243. co_local = obj.matrix_world.normalized().inverted() @ coord
  244. z = -co_local.z
  245. camera = obj.data
  246. frame = [-v for v in camera.view_frame(scene=scene)[:3]]
  247. if camera.type != 'ORTHO':
  248. if z == 0.0:
  249. return Vector((0.5, 0.5, 0.0))
  250. else:
  251. frame = [(v / (v.z / z)) for v in frame]
  252. min_x, max_x = frame[1].x, frame[2].x
  253. min_y, max_y = frame[0].y, frame[1].y
  254. x = (co_local.x - min_x) / (max_x - min_x)
  255. y = (co_local.y - min_y) / (max_y - min_y)
  256. return Vector((x, y, z))