def orthogonal(v): # Vector.orthogonal isn't present in 2.70 size = len(v) v = (Vector((v[0], v[1], 0.0)) if size == 2 else Vector(v)) if v.length_squared < 1e-8: return Vector.Fill(size) ort = Vector((0, 0, 1)).cross(v).normalized() if ort.length_squared < 0.5: ort = Vector((0, 1, 0)).cross(v).normalized() return (ort.to_2d() if size == 2 else ort)
def orthogonal(v): # Vector.orthogonal isn't present in 2.70 size = len(v) v = (Vector((v[0], v[1], 0.0)) if size == 2 else Vector(v)) if v.length_squared < 1e-8: return Vector.Fill(size) ort = Vector((0,0,1)).cross(v).normalized() if ort.length_squared < 0.5: ort = Vector((0,1,0)).cross(v).normalized() return (ort.to_2d() if size == 2 else ort)
def transUvVector(self): max_position = 0. min_position_x = 0. min_position_y = 0. # calculate two rotation matrix from normal vector of selected polygons vector_nor = self.averageNormal() theta_x = self.calcRotAngle('X', vector_nor.x, vector_nor.y, vector_nor.z) mat_rotx = Matrix.Rotation(theta_x, 3, 'X') vector_nor.rotate(mat_rotx) theta_y = self.calcRotAngle('Y', vector_nor.x, vector_nor.y, vector_nor.z) mat_roty = Matrix.Rotation(theta_y, 3, 'Y') # apply two rotation matrix to vertex uv_array = self.mesh.uv_layers.active.data for poly in self.select_poly: for id in range(poly.loop_start, poly.loop_start + poly.loop_total): new_vector = Vector(( self.mesh.vertices[self.mesh.loops[id].vertex_index].co[0], self.mesh.vertices[self.mesh.loops[id].vertex_index].co[1], self.mesh.vertices[self.mesh.loops[id].vertex_index].co[2] )) new_vector.rotate(mat_rotx) new_vector.rotate(mat_roty) uv_array[id].uv = new_vector.to_2d() if min_position_x > uv_array[id].uv.x: min_position_x = uv_array[id].uv.x if min_position_y > uv_array[id].uv.y: min_position_y = uv_array[id].uv.y # recalculate uv position for poly in self.select_poly: for id in range(poly.loop_start, poly.loop_start + poly.loop_total): uv_array[id].uv.x = uv_array[id].uv.x + abs(min_position_x) uv_array[id].uv.y = uv_array[id].uv.y + abs(min_position_y) if max_position < uv_array[id].uv.x: max_position = uv_array[id].uv.x if max_position < uv_array[id].uv.y: max_position = uv_array[id].uv.y # scale uv position for poly in self.select_poly: for id in range(poly.loop_start, poly.loop_start + poly.loop_total): uv_array[ id].uv.x = uv_array[id].uv.x * MAX_LOCATION / max_position uv_array[ id].uv.y = uv_array[id].uv.y * MAX_LOCATION / max_position
def func_constrain_axis_mmb(self, context, key, value, angle): if len(self.tab) > 1: angle = 0 if key == 'MIDDLEMOUSE': if value == 'PRESS': if self.handle_axes == None: args = (self, context, angle) self.handle_axes = bpy.types.SpaceSequenceEditor.draw_handler_add( draw_axes, args, 'PREVIEW', 'POST_PIXEL') self.choose_axis = True self.pos_clic = self.mouse_pos if value == 'RELEASE': self.choose_axis = False if self.pos_clic == self.mouse_pos: self.axis_x = self.axis_y = True if self.handle_axes: bpy.types.SpaceSequenceEditor.draw_handler_remove( self.handle_axes, 'PREVIEW') self.handle_axes = None if self.choose_axis: vec_axis_z = Vector((0, 0, 1)) vec_axis_x = Vector((1, 0, 0)) vec_axis_x.rotate(Quaternion(vec_axis_z, math.radians(angle))) vec_axis_x = vec_axis_x.to_2d() vec_axis_y = Vector((0, 1, 0)) vec_axis_y.rotate(Quaternion(vec_axis_z, math.radians(angle))) vec_axis_y = vec_axis_y.to_2d() ang_x = math.degrees( vec_axis_x.angle(self.mouse_pos - self.center_area)) ang_y = math.degrees( vec_axis_y.angle(self.mouse_pos - self.center_area)) if ang_x > 90: ang_x = 180 - ang_x if ang_y > 90: ang_y = 180 - ang_y if ang_x < ang_y: self.axis_x = True self.axis_y = False else: self.axis_x = False self.axis_y = True
def _get_check_pattern(): pattern = [] rays_rows = 16 # 128 rays rays_cols = 8 for i in range(rays_rows): angle = radians(360.0 / rays_rows * i) for j in range(rays_cols): mat_rotation = Matrix.Rotation(angle, 3, 'Z') p1 = Vector((1.0, 0.0, 0.0)) p1.rotate(mat_rotation) pattern.append(p1.to_2d() * (1.0 / rays_cols * j)) pattern.append(Vector((0.0, 0.0))) return pattern
def transUvVector(self): max_position = 0. min_position_x = 0. min_position_y = 0. # calculate two rotation matrix from normal vector of selected polygons vector_nor = self.averageNormal() theta_x = self.calcRotAngle('X', vector_nor.x, vector_nor.y, vector_nor.z) mat_rotx = Matrix.Rotation(theta_x, 3, 'X') vector_nor.rotate(mat_rotx) theta_y = self.calcRotAngle('Y', vector_nor.x, vector_nor.y, vector_nor.z) mat_roty = Matrix.Rotation(theta_y, 3, 'Y') # apply two rotation matrix to vertex uv_array = self.mesh.uv_layers.active.data for poly in self.select_poly: for id in range(poly.loop_start, poly.loop_start + poly.loop_total): new_vector = Vector((self.mesh.vertices[self.mesh.loops[id].vertex_index].co[0], self.mesh.vertices[self.mesh.loops[id].vertex_index].co[1], self.mesh.vertices[self.mesh.loops[id].vertex_index].co[2])) new_vector.rotate(mat_rotx) new_vector.rotate(mat_roty) uv_array[id].uv = new_vector.to_2d() if min_position_x > uv_array[id].uv.x: min_position_x = uv_array[id].uv.x if min_position_y > uv_array[id].uv.y: min_position_y = uv_array[id].uv.y # recalculate uv position for poly in self.select_poly: for id in range(poly.loop_start, poly.loop_start + poly.loop_total): uv_array[id].uv.x = uv_array[id].uv.x + abs(min_position_x) uv_array[id].uv.y = uv_array[id].uv.y + abs(min_position_y) if max_position < uv_array[id].uv.x: max_position = uv_array[id].uv.x if max_position < uv_array[id].uv.y: max_position = uv_array[id].uv.y # scale uv position for poly in self.select_poly: for id in range(poly.loop_start, poly.loop_start + poly.loop_total): uv_array[id].uv.x = uv_array[id].uv.x * MAX_LOCATION / max_position uv_array[id].uv.y = uv_array[id].uv.y * MAX_LOCATION / max_position
class MouseCoordinate: ''' origin, current, shiftからrelativeを求める。 relativeをorigin -> lockのベクトルに正射影。 exptargetsの例: exptargets = {'dist': [(self.mc, 'dist', self, 'dist', 0.0), (self.mc.exp, 0, self, 'dist', Null())], 'fac': [(self.mc, 'fac', self, 'dist', 0.0), (self.mc.exp, 0, self, 'dist', Null())]} set_values() 引数無しの場合: self.exptargetsを全て処理。 引数をタプルとして受けとった場合: 例 set_values(self.mc, 'dist', self, 'dist', 0.0) これのみ処理。 引数を辞書として受け取った場合: 例 set_values(default=False, fac=True) キーが存在したらそれを真偽によって処理、 その他の真偽はdefault(キーが無ければ真)。 ''' def __init__(self, context=None, event=None, recalcDPBU=True, dpf=200, expnames=('Dist {exp}',)): self.shift = None # *0.1. type:Vector. relativeに影響。 self.lock = None # lock direction. type:Vector. relativeに影響。 self.snap = False # type:Bool self.origin = Vector() # Rキーで変更 self.current = Vector() # (event.mouse_region_x, event.mouse_region_y, 0) self.relative = Vector() # shift,lockを考慮 self.dpbu = 1.0 # 初期化時、及びupdateの際に指定した場合に更新。 self.unit_pow = 1.0 # 上記と同様 self.dist = 0.0 # relativesnapを考慮 self.fac = 0.0 self.inputexp = False self.exp = InputExpression(names=expnames) #self.finaldist = 0.0 # exp等を考慮した最終的な値 self.exptargets = {} self.shortcuts = [] if event: self.origin = Vector((event.mouse_region_x, event.mouse_region_y, \ 0.0)) self.dpf = dpf # dot per fac self.update(context, event, recalcDPBU) def set_exptargets(self, exptargets): self.exptargets = exptargets def set_shortcuts(self, shortcuts): ''' default: SC = Shortcut shortcuts = [SC('lock', 'MIDDLEMOUSE'), SC('reset', 'R'), ''' self.shortcuts = shortcuts def set_values(self, *target, **kw): if target: self.exptargets['targetonly'] = target[0] for key, values in self.exptargets.items(): if target: if key != 'targetonly': continue elif kw: if key in kw: if not kw[key]: continue elif 'default' in kw: if not kw['default']: continue for obj, attr, tagobj, tagattr, err in values: # read if obj == self.exp: # (self.exp, index) if not self.inputexp: continue val = self.exp.get_exp_value(attr) if val is None: if isinstance(err, Null): continue val = err else: if isinstance(attr, str): val = getattr(obj, attr) else: val = obj[attr] # set if isinstance(tagattr, str): setattr(tagobj, tagattr, val) else: tagobj[tagattr] = val if target: del(self.exptargets['targetonly']) def handling_event(self, context, event): mouseco = Vector((event.mouse_region_x, event.mouse_region_y, 0.0)) handled = True EXECUTE = True # execute等を実行後、すぐにreturn {'RUNNING_MODAL'} if self.inputexp: # evalの式の入力 if event.value == 'PRESS': handled_by_exp = self.exp.input(event) if handled_by_exp: self.set_values() return handled, EXECUTE shortcut_name = check_shortcuts(self.shortcuts, event) if event.type == 'TAB' and event.value == 'PRESS': # <Input Expression> if self.inputexp and (event.shift or event.ctrl): self.inputexp = False self.update(context, event) self.set_values() elif not self.inputexp and not (event.shift or event.ctrl): self.inputexp = True self.set_values() else: handled = False elif event.type in ('ESC', 'RIGHTMOUSE') and event.value == 'PRESS': if self.inputexp: self.inputexp = False self.update(context, event) self.set_values() else: handled = False elif event.type in ('LEFT_SHIFT', 'RIGHT_SHIFT'): if event.value == 'PRESS': self.shift = mouseco.copy() elif event.value == 'RELEASE': self.shift = None self.update(context, event) self.set_values() else: handled = False elif event.type in ('LEFT_CTRL', 'RIGHT_CTRL'): if event.value == 'PRESS': self.snap = True elif event.value == 'RELEASE': self.snap = False self.update(context, event) self.set_values() elif event.type == 'MOUSEMOVE': # <Move Mouse> self.update(context, event) self.set_values() elif shortcut_name == 'lock': # <Lock Trans Axis> if self.lock is None: self.lock = mouseco.copy() else: self.lock = None elif shortcut_name == 'reset': # <Reset> if self.lock: self.lock = self.lock - self.origin + mouseco self.origin = mouseco.copy() self.update(context, event) self.set_values() else: handled = False return handled, False def update(self, context=None, event=None, recalcDPBU=False): shift = self.shift snap = self.snap lock = self.lock origin = self.origin if event: current = Vector((event.mouse_region_x, event.mouse_region_y, 0.0)) else: current = self.current if shift: relative = shift - origin + (current - shift) * 0.1 else: relative = current - origin if lock: origin_lock = lock - origin if origin_lock.length >= MIN_NUMBER: if relative.length >= MIN_NUMBER: relative = relative.project(origin_lock) else: self.lock = None if context and recalcDPBU: dpbu, dx, unit_pow = get_DPBU_dx_unit_pow(context.region) else: dpbu, unit_pow = self.dpbu, self.unit_pow dist = relative.length / dpbu fac = relative.length / self.dpf if lock: if relative.dot(origin_lock) < 0.0: dist = -dist fac = -fac if snap: grid = 10 ** unit_pow gridf = 0.1 if shift: grid /= 10 gridf /= 10 dist = grid * math.floor(0.5 + dist / grid) fac = gridf * math.floor(0.5 + fac / gridf) self.current = current self.relative = relative self.dpbu = dpbu self.unit_pow = unit_pow self.dist = dist self.fac = fac def draw_origin(self, radius=5, raydirections=[], raylength=5): draw_sun(self.origin[0], self.origin[1], radius, 16, \ raydirections, raylength) def draw_relative(self, radius=5): #if self.shift: draw_circle(self.origin[0] + self.relative[0], \ self.origin[1] + self.relative[1], radius, 16) def draw_lock_arrow(self, length=10, angle=math.radians(110)): if self.lock is not None: lock = self.lock.to_2d() origin = self.origin.to_2d() vec = (origin - lock).normalized() vec *= 20 vecn = lock + vec draw_arrow(vecn[0], vecn[1], lock[0], lock[1], \ headlength=length, headangle=angle, headonly=True) def draw_factor_circle(self, subdivide=64): draw_circle(self.origin[0], self.origin[1], self.dpf, subdivide)
class GlLine(GlBaseLine): """ 2d/3d Line """ def __init__(self, d=3, p=None, v=None, p0=None, p1=None, z_axis=None): """ d=3 use 3d coords, d=2 use 2d pixels coords Init by either p: Vector or tuple origin v: Vector or tuple size and direction or p0: Vector or tuple 1 point location p1: Vector or tuple 2 point location Will convert any into Vector 3d both optionnals """ if p is not None and v is not None: self.p = Vector(p) self.v = Vector(v) elif p0 is not None and p1 is not None: self.p = Vector(p0) self.v = Vector(p1) - self.p else: self.p = Vector((0, 0, 0)) self.v = Vector((0, 0, 0)) if z_axis is not None: self.z_axis = z_axis else: self.z_axis = Vector((0, 0, 1)) GlBaseLine.__init__(self, d) @property def p0(self): return self.p @property def p1(self): return self.p + self.v @p0.setter def p0(self, p0): """ Note: setting p0 move p0 only """ p1 = self.p1 self.p = Vector(p0) self.v = p1 - p0 @p1.setter def p1(self, p1): """ Note: setting p1 move p1 only """ self.v = Vector(p1) - self.p @property def length(self): return self.v.length @property def angle(self): return atan2(self.v.y, self.v.x) @property def cross(self): """ Vector perpendicular on plane defined by z_axis lie on the right side p1 |--x p0 """ return self.v.cross(self.z_axis) def normal(self, t=0): """ Line perpendicular on plane defined by z_axis lie on the right side p1 |--x p0 """ n = GlLine() n.p = self.lerp(t) n.v = self.cross return n def sized_normal(self, t, size): """ GlLine perpendicular on plane defined by z_axis and of given size positionned at t in current line lie on the right side p1 |--x p0 """ n = GlLine() n.p = self.lerp(t) n.v = size * self.cross.normalized() return n def lerp(self, t): """ Interpolate along segment t parameter [0, 1] where 0 is start of arc and 1 is end """ return self.p + self.v * t def offset(self, offset): """ offset > 0 on the right part """ self.p += offset * self.cross.normalized() def point_sur_segment(self, pt): """ point_sur_segment (2d) point: Vector 3d t: param t de l'intersection sur le segment courant d: distance laterale perpendiculaire positif a droite """ dp = (pt - self.p).to_2d() v2d = self.v.to_2d() dl = v2d.length d = (self.v.x * dp.y - self.v.y * dp.x) / dl t = (v2d * dp) / (dl * dl) return t > 0 and t < 1, d, t @property def pts(self): return [self.p0, self.p1]
class MouseCoordinate: ''' origin, current, shiftからrelativeを求める。 relativeをorigin -> lockのベクトルに正射影。 exptargetsの例: exptargets = {'dist': [(self.mc, 'dist', self, 'dist', 0.0), (self.mc.exp, 0, self, 'dist', Null())], 'fac': [(self.mc, 'fac', self, 'dist', 0.0), (self.mc.exp, 0, self, 'dist', Null())]} set_values() 引数無しの場合: self.exptargetsを全て処理。 引数をタプルとして受けとった場合: 例 set_values(self.mc, 'dist', self, 'dist', 0.0) これのみ処理。 引数を辞書として受け取った場合: 例 set_values(default=False, fac=True) キーが存在したらそれを真偽によって処理、 その他の真偽はdefault(キーが無ければ真)。 ''' def __init__(self, context=None, event=None, recalcDPBU=True, dpf=200, expnames=('Dist {exp}', )): self.shift = None # *0.1. type:Vector. relativeに影響。 self.lock = None # lock direction. type:Vector. relativeに影響。 self.snap = False # type:Bool self.origin = Vector() # Rキーで変更 self.current = Vector( ) # (event.mouse_region_x, event.mouse_region_y, 0) self.relative = Vector() # shift,lockを考慮 self.dpbu = 1.0 # 初期化時、及びupdateの際に指定した場合に更新。 self.unit_pow = 1.0 # 上記と同様 self.dist = 0.0 # relativesnapを考慮 self.fac = 0.0 self.inputexp = False self.exp = InputExpression(names=expnames) #self.finaldist = 0.0 # exp等を考慮した最終的な値 self.exptargets = {} self.shortcuts = [] if event: self.origin = Vector((event.mouse_region_x, event.mouse_region_y, \ 0.0)) self.dpf = dpf # dot per fac self.update(context, event, recalcDPBU) def set_exptargets(self, exptargets): self.exptargets = exptargets def set_shortcuts(self, shortcuts): ''' default: SC = Shortcut shortcuts = [SC('lock', 'MIDDLEMOUSE'), SC('reset', 'R'), ''' self.shortcuts = shortcuts def set_values(self, *target, **kw): if target: self.exptargets['targetonly'] = target[0] for key, values in self.exptargets.items(): if target: if key != 'targetonly': continue elif kw: if key in kw: if not kw[key]: continue elif 'default' in kw: if not kw['default']: continue for obj, attr, tagobj, tagattr, err in values: # read if obj == self.exp: # (self.exp, index) if not self.inputexp: continue val = self.exp.get_exp_value(attr) if val is None: if isinstance(err, Null): continue val = err else: if isinstance(attr, str): val = getattr(obj, attr) else: val = obj[attr] # set if isinstance(tagattr, str): setattr(tagobj, tagattr, val) else: tagobj[tagattr] = val if target: del (self.exptargets['targetonly']) def handling_event(self, context, event): mouseco = Vector((event.mouse_region_x, event.mouse_region_y, 0.0)) handled = True EXECUTE = True # execute等を実行後、すぐにreturn {'RUNNING_MODAL'} if self.inputexp: # evalの式の入力 if event.value == 'PRESS': handled_by_exp = self.exp.input(event) if handled_by_exp: self.set_values() return handled, EXECUTE shortcut_name = check_shortcuts(self.shortcuts, event) if event.type == 'TAB' and event.value == 'PRESS': # <Input Expression> if self.inputexp and (event.shift or event.ctrl): self.inputexp = False self.update(context, event) self.set_values() elif not self.inputexp and not (event.shift or event.ctrl): self.inputexp = True self.set_values() else: handled = False elif event.type in ('ESC', 'RIGHTMOUSE') and event.value == 'PRESS': if self.inputexp: self.inputexp = False self.update(context, event) self.set_values() else: handled = False elif event.type in ('LEFT_SHIFT', 'RIGHT_SHIFT'): if event.value == 'PRESS': self.shift = mouseco.copy() elif event.value == 'RELEASE': self.shift = None self.update(context, event) self.set_values() else: handled = False elif event.type in ('LEFT_CTRL', 'RIGHT_CTRL'): if event.value == 'PRESS': self.snap = True elif event.value == 'RELEASE': self.snap = False self.update(context, event) self.set_values() elif event.type == 'MOUSEMOVE': # <Move Mouse> self.update(context, event) self.set_values() elif shortcut_name == 'lock': # <Lock Trans Axis> if self.lock is None: self.lock = mouseco.copy() else: self.lock = None elif shortcut_name == 'reset': # <Reset> if self.lock: self.lock = self.lock - self.origin + mouseco self.origin = mouseco.copy() self.update(context, event) self.set_values() else: handled = False return handled, False def update(self, context=None, event=None, recalcDPBU=False): shift = self.shift snap = self.snap lock = self.lock origin = self.origin if event: current = Vector((event.mouse_region_x, event.mouse_region_y, 0.0)) else: current = self.current if shift: relative = shift - origin + (current - shift) * 0.1 else: relative = current - origin if lock: origin_lock = lock - origin if origin_lock.length >= MIN_NUMBER: if relative.length >= MIN_NUMBER: relative = relative.project(origin_lock) else: self.lock = None if context and recalcDPBU: dpbu, dx, unit_pow = get_DPBU_dx_unit_pow(context.region) else: dpbu, unit_pow = self.dpbu, self.unit_pow dist = relative.length / dpbu fac = relative.length / self.dpf if lock: if relative.dot(origin_lock) < 0.0: dist = -dist fac = -fac if snap: grid = 10**unit_pow gridf = 0.1 if shift: grid /= 10 gridf /= 10 dist = grid * math.floor(0.5 + dist / grid) fac = gridf * math.floor(0.5 + fac / gridf) self.current = current self.relative = relative self.dpbu = dpbu self.unit_pow = unit_pow self.dist = dist self.fac = fac def draw_origin(self, radius=5, raydirections=[], raylength=5): draw_sun(self.origin[0], self.origin[1], radius, 16, \ raydirections, raylength) def draw_relative(self, radius=5): #if self.shift: draw_circle(self.origin[0] + self.relative[0], \ self.origin[1] + self.relative[1], radius, 16) def draw_lock_arrow(self, length=10, angle=math.radians(110)): if self.lock is not None: lock = self.lock.to_2d() origin = self.origin.to_2d() vec = (origin - lock).normalized() vec *= 20 vecn = lock + vec draw_arrow(vecn[0], vecn[1], lock[0], lock[1], \ headlength=length, headangle=angle, headonly=True) def draw_factor_circle(self, subdivide=64): draw_circle(self.origin[0], self.origin[1], self.dpf, subdivide)
def execute(self, context): current = context.scene.frame_current + 1 # context.scene.frame_current = current bpy.context.scene.frame_set(current) SPL = bpy.context.active_object Voronoi_collection = bpy.data.collections["Voronoi Diagram"] samples = [] VPLs = [] invalid_VPLs = [] face_obs = [] for VPL in bpy.data.collections['Indirect Lights'].all_objects.values( ): if (VPL.hide_viewport): invalid_VPLs += [VPL] else: VPLs += [VPL] # determine the validity of each VPL for VPL in VPLs: sample_direction = validateVPL(VPL, SPL) if (sample_direction): samples += [sample_direction] else: invalid_VPLs += [VPL] VPL.hide_viewport = True # Remove all invalid VPLs and Possibly a number of valid ones to imporvement the distribution for iVPL in invalid_VPLs: if iVPL in VPLs: VPLs.remove(iVPL) if (invalid_VPLs == []): return {'FINISHED'} # Create new VPLs accroding to allotted budget. # delete all old Voronoi face bpy.ops.object.select_all(action='DESELECT') for ob in Voronoi_collection.all_objects: ob.select_set(True) bpy.ops.object.delete() # create new Voronoi face sample_2d = [] for sample in samples: sample_2d += [sample.to_2d().to_3d()] # createPointCloud(context, Voronoi_collection, name="Sample_Points", points=sample_2d, dim='2D') voronoi_faces, vo_verts = createVoronoiDiagramByCircle( context, Voronoi_collection, sample_2d, "Voronoi_face", circle_radius=sin(SPL.data.spot_size * 0.5)) for face in voronoi_faces.values(): createCustomProperty(context, face, "Type", 'Voronoi_Face', "face of Voronoi Diagram") # search min distance in all voronoi vertices distances = {} for vert in vo_verts: v = Vector(vert).to_3d() s = 0.0 for sample in sample_2d: s += (sample - v).length distances[s] = v sort_vo_verts = [distances[k] for k in sorted(distances.keys())] # maximum number for try to add add_VPL_max = 10 i = 0 while (True): if (invalid_VPLs == []): break if (i >= add_VPL_max): break if (sort_vo_verts == []): break iVPL = invalid_VPLs[0] vert = sort_vo_verts.pop() # have numerical error if (vert.length_squared < 1.0): local_v = Vector( (vert.x, vert.y, -math.sqrt(1.0 - vert.length_squared))) else: local_v = Vector((vert.x, vert.y, 0.0)) world_v = SPL.matrix_world @ local_v intersection, intersect_ob = rayCastingMeshObjects( bpy.data.objects, [], SPL.location, world_v - SPL.location) if (intersection): # valid iVPL.hide_viewport = False # modify VPL iVPL.location = intersection iVPL["Hit_Object"] = intersect_ob invalid_VPLs.remove(iVPL) samples += [local_v] sample_2d += [local_v.to_2d().to_3d()] VPLs += [iVPL] i += 1 # delete all old Voronoi face bpy.ops.object.select_all(action='DESELECT') for face in Voronoi_collection.all_objects: face.select_set(True) bpy.ops.object.delete() # recompute Voronoi Diagram and intersect by a circle createPointCloud(context, Voronoi_collection, name="Sample_Points", points=sample_2d, dim='2D') voronoi_faces, vo_verts = createVoronoiDiagramByCircle( context, Voronoi_collection, sample_2d, "Voronoi_face", circle_radius=sin(SPL.data.spot_size * 0.5)) for face in voronoi_faces.values(): createCustomProperty(context, face, "Type", 'Voronoi_Face', "face of Voronoi Diagram") # link and recompute area for sample_idx in range(len(VPLs)): VPLs[sample_idx]["Area"] = voronoi_faces[sample_idx] createCustomProperty(context, VPLs[sample_idx], "Area", voronoi_faces[sample_idx], "Voronoi Face") # Compute intensities for VPLs. area_sum = 0.0 for VPL in VPLs: if (len(VPL['Area'].data.polygons) > 0): area_sum += VPL['Area'].data.polygons[0].area SPL_color = Color(SPL.data.color) SPL_energy = SPL.data.energy for VPL in VPLs: area = 0 if (len(VPL['Area'].data.polygons) > 0): area = VPL['Area'].data.polygons[0].area ob_color = Color(VPL['Hit_Object'].material_slots[0].material. diffuse_color[0:3]) VPL.data.color = [ SPL_color.r * ob_color.r, SPL_color.g * ob_color.g, SPL_color.b * ob_color.b ] # VPL.data.color = [ob_color.r, ob_color.g, ob_color.b] VPL.data.energy = SPL_energy * (area / area_sum) # keyframe insert for VPL in bpy.data.collections['Indirect Lights'].all_objects.values( ): VPL.keyframe_insert(data_path='hide_viewport', frame=current) VPL.keyframe_insert(data_path='location', frame=current) VPL.data.keyframe_insert(data_path='color', frame=current) VPL.data.keyframe_insert(data_path='energy', frame=current) bpy.ops.object.select_all(action='DESELECT') SPL.select_set(True) context.view_layer.objects.active = SPL return {'FINISHED'}