Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Open sidebar
Sofus Albert Høgsbro Rose
anim-midi
Commits
50651872
Commit
50651872
authored
Sep 29, 2017
by
Sofus Albert Høgsbro Rose
Browse files
Added features
parent
1823d8b6
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
675 additions
and
86 deletions
+675
-86
anim-midi-script.py
anim-midi-script.py
+305
-0
anim-midi/device.py
anim-midi/device.py
+178
-15
maps.py
maps.py
+1
-0
test.py
test.py
+158
-71
test_device.py
test_device.py
+33
-0
No files found.
anim-midi-script.py
0 → 100644
View file @
50651872
#!/usr/bin/env python3
"""
What can I say. It works. Load it into the text editor and tap run script to enjoy MIDI animation.
Features includes:
- Weirdest fucking mapping interface. Dig deep into the modal operator
to map simple Blender properties to the MIDI stream.
- Super laggy and slow during animation!
- Two frame changes per MIDI event. Why you ask? To update the scene. Can't find a better way...
- Only one device supported - Novation Impulse 25 key
- MIDI stuff runs in a thread! RIP GIL. Perhaps it should run in a - PROCESS..?
- Supports - no, REQUIRES - jack! Jack MIDI input is real & kicking.
^^ That's a feature by the way
- Rig specific for now. But beautifully so :).
- Seriously this is cool shit...
- Oh yeah: pip install JACK-Client . Make sure you're on Linux or Mac(?) and not the one we don't talk about ;)
"""
import
math
,
sys
,
copy
,
time
,
struct
import
multiprocessing
as
mp
sys
.
path
.
append
(
'/usr/local/lib/python3.5/dist-packages'
)
#Needed for internal blender to see modules.
#CONSTANTS
q
=
mp
.
Queue
()
#For JACK --> Curator process communication..
cmdJQ
=
mp
.
Queue
()
#For main --> JACK process communication.
curQ
=
mp
.
Queue
()
#For Curator --> main process communication
FPS
=
5
#Set for smoothness vs. Performance.
SAMPLE_FAC
=
1
#The number of times per frame that the MIDI controls are sampled.
#Decimal Representations of MIDI statuses for various controls on my Novation Impulse. From reading struct.unpack('3B', data) in process().
STATUS_TYPE
=
{
144
:
'NOTEDOWN'
,
128
:
'NOTEUP'
,
176
:
'CONTROL'
,
224
:
'PITCH_MOD'
}
#Channel --> Button Name on my Novation Impulse 25.
CHAN_MAP
=
{
49
:
'FADER_1'
,
21
:
'KNOB_1'
,
22
:
'KNOB_2'
,
23
:
'KNOB_3'
,
24
:
'KNOB_4'
,
25
:
'KNOB_5'
,
26
:
'KNOB_6'
,
27
:
'KNOB_7'
,
28
:
'KNOB_8'
,
1
:
'MOD_WHL'
,
115
:
'PLAY'
,
114
:
'STOP'
,
112
:
'REW'
,
123
:
'FWD'
,
117
:
'REC'
,
116
:
'LOOP'
}
#Button Name --> Channel on my Novation Impulse 25.
BUT_MAP
=
{
v
:
k
for
k
,
v
in
CHAN_MAP
.
items
()}
#The map of Buttons to Blender RNA paths to manipulate. Filled later.
BLEND_MAP
=
{}
#See anim_bind for construction.
MOD_BLENDER
=
False
try
:
import
bpy
MOD_BLENDER
=
True
except
:
print
(
'Blender bpy module not found! Mappings won
\'
t work!'
)
import
jack
#Helper Functions
def
m2f
(
note
):
"""Convert MIDI note number to a hz freq
https://en.wikipedia.org/wiki/MIDI_Tuning_Standard.
Cool idea: Play rig animation as MIDI notes?
"""
return
2
**
((
note
-
69
)
/
12
)
*
440
def
anim_bind
(
button
,
*
bProp
,
iRange
=
(
0
,
127
),
oRange
=
(
0.0
,
1.0
),
iFunc
=
lambda
x
:
x
)
:
#Blneder Properties can't be bound here.
"""
Manipulate the numeric value at the list of RNA data locations bProp in blender, by
making the data coming from CONTROL signals on button (a string defined in BUT_MAP) into
live data manipulation.
iRange is the midi signal range, oRange is the Blender knob range.
iFunc is an optional interpolation function.
"""
BLEND_MAP
[
button
]
=
{
'button'
:
button
,
'blender_prop'
:
bProp
,
'input_range'
:
iRange
,
'output_range'
:
oRange
,
'interp'
:
iFunc
,
}
#Only use the newest (last) item of each channel to update.
class
Data
:
def
__init__
(
self
,
chnls
,
items
,
bVals
,
picklable
=
False
)
:
"""
"""
self
.
picklable
=
picklable
self
.
items
=
tuple
(
items
)
try
:
if
picklable
:
#To be picklable, we must exclude the interpolation function. It's not needed, anyhow.
self
.
binds
=
tuple
([
{
k
:
v
for
k
,
v
in
BLEND_MAP
[
CHAN_MAP
[
item
[
1
]]].
items
()
if
k
!=
'interp'
}
for
item
in
items
])
else
:
self
.
binds
=
tuple
([
BLEND_MAP
[
CHAN_MAP
[
item
[
1
]]]
for
item
in
items
])
except
:
raise
ValueError
(
'Button not bound! MIDI channel #s processed:'
,
[
item
[
1
]
for
item
in
items
])
self
.
bVals
=
tuple
(
bVals
)
self
.
chnls
=
tuple
(
chnls
)
def
__len__
(
self
)
:
return
len
(
self
.
items
)
def
__iter__
(
self
)
:
return
iter
([{
'item'
:
item
,
'bind'
:
bind
,
'bVal'
:
bVal
}
for
item
,
bind
,
bVal
in
zip
(
self
.
items
,
self
.
binds
,
self
.
bVals
)])
def
__repr__
(
self
)
:
return
"Data({0}, {1}, {2}, picklable={3})"
.
format
(
self
.
chnls
,
self
.
items
,
self
.
bVals
,
self
.
picklable
)
#The process function.
def
jack_proc
(
q
,
cmdJQ
,
STATUS_TYPE
)
:
#Setup Jack
client
=
jack
.
Client
(
'anim-midi'
)
midi_in
=
client
.
midi_inports
.
register
(
'input'
)
#~ audio_out = client.outports.register('audio')
@
client
.
set_process_callback
def
process
(
frames
):
for
offset
,
data
in
midi_in
.
incoming_midi_events
():
if
len
(
data
)
==
3
:
status
,
pitch
,
vel
=
struct
.
unpack
(
'3B'
,
data
)
#Status meanings are in STATUS_TYPE.
#Pitch is the CC# - the MIDI channel/note value.
try
:
if
STATUS_TYPE
[
status
]
==
'CONTROL'
:
#~ print("[JACK MIDI]", STATUS_TYPE[status], pitch, vel)
q
.
put
(
(
STATUS_TYPE
[
status
],
pitch
,
vel
,
0
)
)
#signalType, channel #, value, bVal (not yet set)
except
:
print
(
"Status not mapped!"
)
with
client
:
#Blocks until QUIT is passed over cmdQ queue.
client
.
connect
(
"a2j:Impulse [24] (capture): Impulse MIDI 1"
,
midi_in
)
while
True
:
msg
=
cmdJQ
.
get
()
if
msg
==
"QUIT"
:
break
#Think of this as the middleware process
def
cur_proc
(
q
,
curQ
)
:
def
scaleRange
(
val
,
iRange
,
oRange
):
return
(((
val
-
iRange
[
0
])
*
(
oRange
[
1
]
-
oRange
[
0
]))
/
(
iRange
[
1
]
-
iRange
[
0
]))
+
oRange
[
0
]
curTime
=
time
.
perf_counter
()
dT
=
0
while
True
:
time
.
sleep
(
1
/
(
SAMPLE_FAC
*
FPS
))
if
dT
>=
1
/
FPS
:
#Don't run too often.
try
:
first
=
q
.
get
(
block
=
False
)
#Block until a signal arrives.
except
:
dT
+=
time
.
perf_counter
()
-
curTime
curTime
=
time
.
perf_counter
()
continue
#Dequeue all new signal data.
tStart
=
time
.
perf_counter
()
newItems
=
[
first
]
while
not
q
.
empty
()
:
newItems
.
append
(
q
.
get
(
block
=
False
))
chnls
=
[]
items
=
[]
for
item
in
newItems
[::
-
1
]
:
#Reversed so we get the newest item.
if
item
[
1
]
not
in
chnls
:
#Never seen this channel before.
chnls
.
append
(
item
[
1
])
#Append the channel to the list of channels.
items
.
append
(
item
)
#Append the item to the list of items.
else
:
continue
try
:
incData
=
Data
(
chnls
,
items
,
[
0
for
i
in
range
(
len
(
items
))])
#Not yet complete.
except
Exception
as
e
:
print
(
repr
(
e
))
continue
bVals
=
[]
for
d
in
incData
:
#~ print(d)
val_nrm
=
scaleRange
(
d
[
'item'
][
2
],
d
[
'bind'
][
'input_range'
],
(
0
,
1
))
val_interp
=
d
[
'bind'
][
'interp'
](
val_nrm
)
#Call the attached interpolation function on the normalized value.
bVals
.
append
(
scaleRange
(
val_interp
,
(
0
,
1
),
d
[
'bind'
][
'output_range'
]))
data
=
Data
(
chnls
,
items
,
bVals
,
picklable
=
True
)
#~ print(data)
curQ
.
put
(
tuple
(
data
)
)
dT
+=
time
.
perf_counter
()
-
curTime
curTime
=
time
.
perf_counter
()
class
ModalTimerOperator
(
bpy
.
types
.
Operator
):
"""Operator which runs its self from a timer"""
bl_idname
=
"wm.modal_timer_operator"
bl_label
=
"Modal Timer Operator"
_timer
=
None
CHOICE
=
0
def
modal
(
self
,
context
,
event
):
if
event
.
type
in
{
'ESC'
}:
self
.
cancel
(
context
)
return
{
'CANCELLED'
}
if
event
.
type
==
'TIMER'
:
print
(
'hi'
)
try
:
data
=
q
.
get
(
block
=
False
)
except
:
print
(
"Queue empty"
)
return
{
'PASS_THROUGH'
}
print
(
data
)
if
not
data
:
return
{
'PASS_THROUGH'
}
#Update all properties from each bind/item pair.
for
d
in
data
:
if
CHAN_MAP
[
d
[
'item'
][
1
]]
==
d
[
'bind'
][
'button'
]
:
for
bProp
in
d
[
'bind'
][
'blender_prop'
]
:
#~ print("{0} = {1}".format(bProp, data['bVal']))
exec
(
"{0} = {1}"
.
format
(
bProp
,
data
[
'bVal'
]))
#Try bpy.data.objects['rig'].children[0].data.update()
if
not
self
.
CHOICE
:
bpy
.
context
.
scene
.
frame_set
(
bpy
.
context
.
scene
.
frame_current
+
1
);
self
.
CHOICE
=
1
if
self
.
CHOICE
:
bpy
.
context
.
scene
.
frame_set
(
bpy
.
context
.
scene
.
frame_current
-
1
);
self
.
CHOICE
=
0
return
{
'PASS_THROUGH'
}
def
execute
(
self
,
context
):
wm
=
context
.
window_manager
self
.
_timer
=
wm
.
event_timer_add
(
1
/
(
SAMPLE_FAC
*
FPS
),
context
.
window
)
wm
.
modal_handler_add
(
self
)
return
{
'RUNNING_MODAL'
}
def
cancel
(
self
,
context
):
wm
=
context
.
window_manager
wm
.
event_timer_remove
(
self
.
_timer
)
jackProc
.
terminate
()
curProc
.
terminate
()
print
(
"Quit anim-midi!"
)
def
register
():
bpy
.
utils
.
register_class
(
ModalTimerOperator
)
def
unregister
():
bpy
.
utils
.
unregister_class
(
ModalTimerOperator
)
if
__name__
==
"__main__"
:
#Bind an example BLEND_MAP.
rig
=
"bpy.data.objects['rig']"
face
=
"bpy.data.objects['rig'].pose.bones['CTRL_face']"
eye_R
=
"bpy.data.objects['rig'].pose.bones['CTRL_eye_R']"
eye_L
=
"bpy.data.objects['rig'].pose.bones['CTRL_eye_L']"
anim_bind
(
'FADER_1'
,
face
+
"['mouth']"
)
anim_bind
(
'KNOB_1'
,
eye_R
+
"['eye_back']"
,
eye_R
+
"['eye_lower']"
,
eye_R
+
"['eye_upper']"
)
anim_bind
(
'KNOB_5'
,
eye_L
+
"['eye_back']"
,
eye_L
+
"['eye_lower']"
,
eye_L
+
"['eye_upper']"
)
anim_bind
(
'KNOB_8'
,
face
+
"['nose']"
)
anim_bind
(
'KNOB_8'
,
face
+
"['nose']"
)
anim_bind
(
'MOD_WHL'
,
rig
+
".pose.bones['body_hind'].rotation_euler[0]"
,
oRange
=
(
math
.
radians
(
-
7.0
),
math
.
radians
(
20.0
)))
print
(
BLEND_MAP
)
register
()
jackProc
=
mp
.
Process
(
target
=
jack_proc
,
args
=
(
q
,
cmdJQ
,
copy
.
deepcopy
(
STATUS_TYPE
)))
jackProc
.
start
()
curProc
=
mp
.
Process
(
target
=
cur_proc
,
args
=
(
q
,
curQ
))
curProc
.
start
()
anim-midi/device.py
View file @
50651872
STATUS_TYPE
=
{
144
:
'NOTEDOWN'
,
128
:
'NOTEUP'
,
176
:
'CONTROL'
,
224
:
'PITCH_MOD'
'''
This file defines device mappings, as well as a universal device mapping. The
idea being, any device can map to the universal device mapping, which then
maps to whatever program.
checkStatus returns the Status Type of the raw jack-supplied arg, status.
Status Types:
- NOTEDOWN - When the note is pressed.
- NOTEUP - When the note is pulled up.
- CONTROL - MIDI control signal.
- PITCH_MOD - Pitch control.
- POLY_AFTER - Aftertouch, with pressure.
- PROG_CHG - Program Change signal.
- CHNL_AFTER - Aftertouch, all or nothing.
CONTROL Buttons:
Channel:
- FADER_n - the nth channel fader (slides) on the device.
- KNOB_n - the nth channel knob (turns) on the device.
- SOLO_n - the nth channel solo button.
- MUTE_n - the nth channel mute button.
- REC_n - the nth channel record button.
Transport:
- PLAY - the play button.
- STOP - the stop button.
- REW - the rewind button.
- FWD - the fast forward button.
- REC - the global record button.
- LOOP - the loop function button.
Markers:
- MARK_SET - the play button.
- MARK_PREV - the play button.
- MARK_NEXT - the play button.
Track:
- TRACK_PREV - Previous track.
- TRACK_NEXT - Next track.
Universal Device:
This is a universal mapping from names to a specialized mapping.
Supports up to 20 channels.
CONTROL (see above for ordering) :
0 - 5: Transport.
6 - 8: Markers
9 - 10: Track
11 - 27: Reserved
28 - 47: Fader Channels
48 - 67: Knob Channels
68 - 87: Solo Channels
88 - 107: Mute Channels
108 - 127: Record Channels
28 - 127: Channels
'''
#First 4 bits. Compare with
STATUS_TYPE
=
{
0x9
:
'NOTEDOWN'
,
0x8
:
'NOTEUP'
,
0xB
:
'CONTROL'
,
0xE
:
'PITCH_MOD'
,
0xA
:
'POLY_AFTER'
,
0xC
:
'PROG_CHG'
,
0xD
:
'CHNL_AFTER'
}
STATUS_TYPE_REV
=
{
v
:
k
for
k
,
v
in
STATUS_TYPE
.
items
()}
#0-127 are the available pitches. See Documentation.
UNI_DEV
=
{
'offset'
:
0
,
'PLAY'
:
0
,
'STOP'
:
1
,
'REW'
:
2
,
'FWD'
:
3
,
'REC'
:
4
,
'LOOP'
:
5
,
'MARK_SET'
:
6
,
'MARK_PREV'
:
7
,
'MARK_NEXT'
:
8
,
'TRACK_PREV'
:
9
,
'TRACK_NEXT'
:
10
}
#Finish off the dict with channel values
count
=
0
for
label
in
(
'FADER'
,
'KNOB'
,
'SOLO'
,
'MUTE'
,
'REC'
)
:
for
i
in
range
(
0
,
20
)
:
UNI_DEV
[
label
+
str
(
i
)]
=
28
+
count
count
+=
1
DEV
=
{
"novation_impulse_25"
:
{
49
:
'FADER_1'
,
21
:
'KNOB_1'
,
22
:
'KNOB_2'
,
23
:
'KNOB_3'
,
24
:
'KNOB_4'
,
25
:
'KNOB_5'
,
26
:
'KNOB_6'
,
27
:
'KNOB_7'
,
28
:
'KNOB_8'
,
{
'offset'
:
1
,
49
:
'FADER_0'
,
21
:
'KNOB_0'
,
22
:
'KNOB_1'
,
23
:
'KNOB_2'
,
24
:
'KNOB_3'
,
25
:
'KNOB_4'
,
26
:
'KNOB_5'
,
27
:
'KNOB_6'
,
28
:
'KNOB_7'
,
1
:
'MOD_WHL'
,
115
:
'PLAY'
,
114
:
'STOP'
,
112
:
'REW'
,
1
2
3
:
'FWD'
,
1
1
3
:
'FWD'
,
117
:
'REC'
,
116
:
'LOOP'
}
},
"korg_nanokontrol_2"
:
{
'offset'
:
0
,
0
:
'FADER_0'
,
1
:
'FADER_1'
,
2
:
'FADER_2'
,
3
:
'FADER_3'
,
4
:
'FADER_4'
,
5
:
'FADER_5'
,
6
:
'FADER_6'
,
7
:
'FADER_7'
,
16
:
'KNOB_0'
,
17
:
'KNOB_1'
,
18
:
'KNOB_2'
,
19
:
'KNOB_3'
,
20
:
'KNOB_4'
,
21
:
'KNOB_5'
,
22
:
'KNOB_6'
,
23
:
'KNOB_7'
,
32
:
'SOLO_0'
,
33
:
'SOLO_1'
,
34
:
'SOLO_2'
,
35
:
'SOLO_3'
,
36
:
'SOLO_4'
,
37
:
'SOLO_5'
,
38
:
'SOLO_6'
,
39
:
'SOLO_7'
,
48
:
'MUTE_0'
,
49
:
'MUTE_1'
,
50
:
'MUTE_2'
,
51
:
'MUTE_3'
,
52
:
'MUTE_4'
,
53
:
'MUTE_5'
,
54
:
'MUTE_6'
,
55
:
'MUTE_7'
,
64
:
'REC_0'
,
65
:
'REC_1'
,
66
:
'REC_2'
,
67
:
'REC_3'
,
68
:
'REC_4'
,
69
:
'REC_5'
,
70
:
'REC_6'
,
71
:
'REC_7'
,
41
:
'PLAY'
,
42
:
'STOP'
,
43
:
'REW'
,
44
:
'FWD'
,
45
:
'REC'
,
46
:
'LOOP'
,
60
:
'MARK_SET'
,
61
:
'MARK_PREV'
,
62
:
'MARK_NEXT'
,
58
:
'TRACK_PREV'
,
59
:
'TRACK_NEXT'
},
"universal"
:
UNI_DEV
}
DEV_REV
=
{
k
:
{
a
:
e
for
e
,
a
in
v
.
items
()}
for
k
,
v
in
DEV
.
items
()}
#Gives back the status type from the raw status consistently.
def
checkStatus
(
status
):
return
STATUS_TYPE
[
status
>>
4
]
def
getButton
(
status
,
pitch
,
device
)
:
if
checkStatus
(
status
)
==
"CONTROL"
and
device
:
return
DEV
[
device
][
pitch
]
else
:
return
pitch
def
asDevice
(
pitch
,
fromDev
,
toDev
)
:
'''
Transforms a pitch from fromDev to toDev. Just annoying to do.
'''
return
DEV_REV
[
toDev
][
DEV
[
fromDev
][
pitch
]]
def
anim_bind
(
button
,
*
bProp
,
iRange
=
(
0
,
127
),
oRange
=
(
0.0
,
1.0
),
iFunc
=
lambda
x
:
x
)
:
#Blneder Properties can't be bound here.
"""
Manipulate the numeric value at the list of RNA data locations bProp in blender, by
...
...
maps.py
0 → 100644
View file @
50651872
test.py
View file @
50651872
...
...
@@ -30,8 +30,10 @@ sys.path.append('/usr/local/lib/python3.5/dist-packages') #Needed for internal b
#CONSTANTS
FPS
=
60
#Set for smoothness vs. Performance.
SAMPLE_FAC
=
3
#The number of times per frame that the MIDI controls are sampled.
FPS
=
24
#Set for smoothness vs. Performance.
SAMPLE_FAC
=
1
#The number of times per frame that the MIDI controls are sampled.
#Settings ^^ higher would require decreasing the processing time in the modal.
#Decimal Representations of MIDI statuses for various controls on my Novation Impulse. From reading struct.unpack('3B', data) in process().
...
...
@@ -56,7 +58,7 @@ CHAN_MAP = { 49 : 'FADER_1',
115
:
'PLAY'
,
114
:
'STOP'
,
112
:
'REW'
,
1
2
3
:
'FWD'
,
1
1
3
:
'FWD'
,
117
:
'REC'
,
116
:
'LOOP'
}
...
...
@@ -76,35 +78,117 @@ except :
import
jack
#Helper Functions
def
scaleRange
(
val
,
iRange
,
oRange
):
return
(((
val
-
iRange
[
0
])
*
(
oRange
[
1
]
-
oRange
[
0
]))
/
(
iRange
[
1
]
-
iRange
[
0
]))
+
oRange
[
0
]
def
m2f
(
note
):
"""Convert MIDI note number to a hz freq
https://en.wikipedia.org/wiki/MIDI_Tuning_Standard.
Cool idea: Play rig animation as MIDI notes?
"""
return
2
**
((
note
-
69
)
/
12
)
*
440
###############################################################################################
#PUT IN SEPERATE SCRIPT
def
anim_bind
(
button
,
*
bProp
,
iRange
=
(
0
,
127
),
oRange
=
(
0.0
,
1.0
),
iFunc
=
lambda
x
:
x
)
:
#Blneder Properties can't be bound here.
def
anim_bind
(
button
,
*
bProp
,
iRange
=
(
0
,
127
),
oRange
=
(
0.0
,
1.0
),
isOp
=
False
,
isBool
=
False
,
iFunc
=
lambda
x
:
x
)
:
#Blneder Properties can't be bound here.
"""
Manipulate the numeric value at the list of
RNA
data locations bProp in blender, by
Manipulate the numeric value at the list of data locations bProp in blender, by
making the data coming from CONTROL signals on button (a string defined in BUT_MAP) into
live data manipulation.
iRange is the midi signal range, oRange is the Blender knob range.
iFunc is an optional interpolation function.
If isOp is true, then the blender_prop is an operator to call, and iRange/oRange/interp is useless.
If isBool is true, then the blender_prop is a boolean value. iRange[1] and iRange[0] will map to True/False by rounding.
"""
BLEND_MAP
[
button
]
=
{
'button'
:
button
,
#~ dest_map = dest if dest else BLEND_MAP
return
{
'button'
:
button
,
'isOp'
:
isOp
,
'isBool'
:
isBool
,
'blender_prop'
:
bProp
,
'input_range'
:
iRange
,
'output_range'
:
oRange
,
'interp'
:
iFunc
}
def
gen_RAT_FACE
()
:
RAT_FACE
=
{}
eye_R
=
"bpy.data.objects['rig'].pose.bones['CTRL_eye_R']"
eye_L
=
"bpy.data.objects['rig'].pose.bones['CTRL_eye_L']"
rig
=
"bpy.data.objects['rig']"
face
=
"bpy.data.objects['rig'].pose.bones['CTRL_face']"
RAT_FACE
[
'FADER_1'
]
=
anim_bind
(
'FADER_1'
,
face
+
"['mouth']"
)
RAT_FACE
[
'KNOB_1'
]
=
anim_bind
(
'KNOB_1'
,
eye_R
+
"['eye_back']"
,
eye_R
+
"['eye_lower']"
,
eye_R
+
"['eye_upper']"
)
RAT_FACE
[
'KNOB_5'
]
=
anim_bind
(
'KNOB_5'
,
eye_L
+
"['eye_back']"
,
eye_L
+
"['eye_lower']"
,
eye_L
+
"['eye_upper']"
)
RAT_FACE
[
'KNOB_2'
]
=
anim_bind
(
'KNOB_2'
,
rig
+
'.pose.bones["CTRL_face"]["corner_R"]'
)
RAT_FACE
[
'KNOB_6'
]
=
anim_bind
(
'KNOB_6'
,
rig
+
'.pose.bones["CTRL_face"]["corner_L"]'
)
RAT_FACE
[
'KNOB_3'
]
=
anim_bind
(
'KNOB_3'
,
rig
+
'.pose.bones["CTRL_face"]["neck_bulge"]'
)
RAT_FACE
[
'KNOB_7'
]
=
anim_bind
(
'KNOB_7'
,
rig
+
'.pose.bones["CTRL_face"]["neck_lungs"]'
)
RAT_FACE
[
'KNOB_4'
]
=
anim_bind
(
'KNOB_4'
,
rig
+
'.pose.bones["CTRL_face"]["cheek_back_R"]'
,
iFunc
=
lambda
x
:
x
*
2
)
RAT_FACE
[
'KNOB_8'
]
=
anim_bind
(
'KNOB_8'
,
rig
+
'.pose.bones["CTRL_face"]["cheek_back_L"]'
,
iFunc
=
lambda
x
:
x
*
2
)
RAT_FACE
[
'REW'
]
=
anim_bind
(
'REW'
,
'bpy.ops.screen.keyframe_jump(next=False)'
,
isOp
=
True
)
RAT_FACE
[
'FWD'
]
=
anim_bind
(
'FWD'
,
'bpy.ops.screen.keyframe_jump(next=True)'
,
isOp
=
True
)
RAT_FACE
[
'LOOP'
]
=
anim_bind
(
'LOOP'
,
'bpy.ops.screen.frame_jump(end=False)'
,
isOp
=
True
)
RAT_FACE
[
'PLAY'
]
=
anim_bind
(
'PLAY'
,
'bpy.ops.screen.animation_play()'
,
isOp
=
True
)
RAT_FACE
[
'REC'
]
=
anim_bind
(
'REC'
,
'context.scene.tool_settings.use_keyframe_insert_auto'
,
isBool
=
True
)
#~ anim_bind('MOD_WHL', rig + ".pose.bones['body_hind'].rotation_euler[0]", oRange=(math.radians(-7.0), math.radians(20.0)))
RAT_FACE
[
'MOD_WHL'
]
=
anim_bind
(
'MOD_WHL'
,
rig
+
'.pose.bones["CTRL_face"]["nose"]'
)
return
RAT_FACE
MAPS
=
{
'RAT_FACE'
:
gen_RAT_FACE
()
}
###############################################################################################
#The queues.
q
=
mp
.
Queue
()
#For the JACK process to send data to Blender.
cmdQ
=
mp
.
Queue
()
#To send commands to the JACK process.