-
-
Notifications
You must be signed in to change notification settings - Fork 55
/
backdrop.py
220 lines (177 loc) · 7.07 KB
/
backdrop.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# ##### BEGIN GPL LICENSE BLOCK #####
#
# Backdrop.py , a Blender addon to create a backdrop that fits the active camera.
# (c) 2016 Michel J. Anders (varkenvarken)
#
# 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 2
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
bl_info = {
"name": "BackDrop",
"author": "Michel Anders (varkenvarken)",
"version": (0, 0, 201601241147),
"blender": (2, 76, 0),
"location": "View3D > Add > Backdrop",
"description": "Create a backdrop that completely fits the view from the active camera",
"warning": "",
"wiki_url": "",
"tracker_url": "",
"category": "Add Mesh"}
from math import radians
import bpy
import bmesh
from bpy.props import IntProperty, FloatProperty, BoolProperty
from mathutils import Vector, Quaternion
from mathutils.geometry import interpolate_bezier
class BackDrop(bpy.types.Operator):
bl_idname = "mesh.backdrop"
bl_label = "BackDrop"
bl_options = {'REGISTER', 'UNDO'}
margin = FloatProperty(name="Camera Margin", default=0.0,
description="Add extra margin to the visual area from te camera (might be negative as well).")
lift = FloatProperty(name="Lift", default=0.0,
description="Elevation of far edge of backdrop")
distance = FloatProperty(name="Level", default=2.0,
description="Distance below camera")
zero = BoolProperty (name="Zero level", default=True,
description="Position backdrop at z = 0")
angle = FloatProperty(name="Curvature", default=radians(0), subtype='ANGLE', precision=0,
description="Curvature of backdrop with non-zero Lift")
subdivisions= IntProperty (name="Subdivisions", default=5, min=0, soft_max=20,
description="Number of subdivisions in backdrop grid")
@classmethod
def poll(self, context):
return (context.mode == 'OBJECT') and (context.scene.camera is not None)
def draw(self, context):
layout = self.layout
row = layout.row()
row.prop(self, 'zero')
if not self.zero:
row.prop(self, 'distance')
layout.prop(self, 'margin')
layout.prop(self, 'lift')
layout.prop(self, 'angle')
layout.prop(self, 'subdivisions')
if self.impossible:
box = layout.box()
box.enabled = False
box.label("Impossible to create a backdrop, uncheck zero level?")
def backdrop(self, context):
# calculate the view frame in world coordinates
# (i.e. the far plane of the view frustum)
scene = context.scene
cam_ob = scene.camera
cam = bpy.data.cameras[cam_ob.name] # camera in scene is object type, not a camera type
cam_mat = cam_ob.matrix_world
view_frame = cam.view_frame(scene) # without a scene the aspect ratio of the camera is not taken into account
view_frame = [cam_mat * v for v in view_frame]
cam_pos = cam_mat * Vector((0,0,0))
view_center = sum(view_frame, Vector((0,0,0)))/len(view_frame)
view_normal = (view_center - cam_pos).normalized()
# enlarge the view frame beyond the actual cam boundaries if requested
if self.margin != 0.0:
view_frame = [((v - view_center)*(1+self.margin))+view_center for v in view_frame]
# calculate the intersection with a horizontal plane below the camera
# the top edge is intersected with an artificially offset ground plane
# so it will intersected with the lifted far end of our backdrop
overts = []
refz = cam_pos.z if self.zero else self.distance
for n,vf in enumerate(view_frame):
v = (vf - cam_pos).normalized()
d = 0.0 if n in (1,2) else self.lift
zf = (d - refz) / v.z
if zf <= 0.0 :
break
overts.append(zf * v)
# create two edge loops (the right and left side of the intersection
# of the view frustum with the backdrop). We curve it downward we a
# bezier interpolation. This curvature can be controlled somewhat
# with a rotation angle but this is far from ideal yet
verts = []
edges = []
vs = Vector(overts[2])
ve = Vector(overts[3])
rotaxis = (vs - ve).cross(Vector((0,0,-1))).normalized()
rot = Quaternion(rotaxis, self.angle)
handle2 = ve + rot * Vector((0,0,-self.lift))
handle1 = vs + (vs - ve).normalized()
points = interpolate_bezier(vs, handle1, handle2, ve, self.subdivisions+1)
verts.extend(points)
edges.extend((i,i+1) for i in range(len(points)-1))
vs = Vector(overts[0])
ve = Vector(overts[1])
rotaxis = (ve - vs).cross(Vector((0,0,-1))).normalized()
rot = Quaternion(rotaxis, self.angle)
handle2 = ve + (ve - vs).normalized()
handle1 = vs + rot * Vector((0,0,-self.lift))
points = interpolate_bezier(ve, handle2, handle1, vs, self.subdivisions+1)
points.reverse()
offset = len(verts)
verts.extend(points)
edges.extend((offset+i,offset+i+1) for i in range(len(points)-1))
return verts, edges
def execute(self, context):
verts, edges = self.backdrop(context)
# it is possible that the camera points upward and we cannot
# create a backdrop. In that scenario no verts are returned and
# we signal something in the user interface.
self.impossible = True
if len(verts):
self.impossible = False
# create an empty mesh object
mesh = bpy.data.meshes.new("BackDrop")
ob = bpy.data.objects.new("BackDrop", mesh)
context.scene.objects.link(ob)
context.scene.update()
context.scene.objects.active = ob
ob.select = True
# create a new empty bmesh and fill it with our geometry
bm = bmesh.new()
for v in verts:
bm.verts.new(v)
bm.verts.ensure_lookup_table()
for e1,e2 in edges:
bm.edges.new((bm.verts[e1],bm.verts[e2]))
bm.edges.ensure_lookup_table()
# bridge the two edgeloops and subdivide the new edges
geom = bmesh.ops.bridge_loops(bm, edges=bm.edges[:])
bmesh.ops.subdivide_edges(bm, edges=geom['edges'], cuts=self.subdivisions)
# transfer to mesh object
bm.to_mesh(mesh)
bm.free()
mesh.update()
# add a subsurf modifier
bpy.ops.object.shade_smooth()
mod = ob.modifiers.new(name='Subsurf', type='SUBSURF')
mod.levels = 2
mod.render_levels = 2
# set the location of the backdrop to coincide with the camera
# and parent it to the cam as well so it will move with it
ob.location = context.scene.camera.location
context.scene.objects.active = context.scene.camera
bpy.ops.object.parent_set(type='OBJECT')
context.scene.objects.active = ob
return {'FINISHED'}
def menu_func(self, context):
self.layout.operator(BackDrop.bl_idname, text="Backdrop",
icon='PLUGIN')
def register():
bpy.utils.register_module(__name__)
bpy.types.INFO_MT_add.append(menu_func)
def unregister():
bpy.types.INFO_MT_add.remove(menu_func)
bpy.utils.unregister_module(__name__)
if __name__ == "__main__":
register()