In the digital age you stumble on concepts analog in their nature. Modern graphics are created on a computer yet they originate in an analog process. You constantly change the brush size yet the computer allows you to do it in an increments. Do you actually care your color is R=53, G=123, B=200 in numerical values except when moving the data?

Concept of making an analog controller for art creation on the computer was always interesting for me. An exercise in this was this small controller built with Arduino. It's just a 3 potentiometers dividing voltage. Arduino reads this voltage and sends the numeric values to Blender which can use them in various ways thanks to built-in Python environment. I used it to control color values.

Quick and creative soldering...

Below you will find an explanation in a video form.

How does it work?

Blender has a built-in Python environment. That means that you can use any Python module. In this case I've used the pyserial library to communicate with the Arduino.

In order to be be able to access the pyserial module I've downloaded it, unpacked and copied serial folder here:

C:\Program Files\Blender Foundation\Blender\2.79\python\lib\

The Blender side receiving script looks as follows:

import bpy
import serial
import colorsys

try:
    port = serial.Serial("\\\\.\\COM5", 115200)
except:
    print("Error opening serial port.")
    
ADC_RANGE = 1024.0

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
    mode = 'HSV'

    def modal(self, context, event):
        if event.type == 'TIMER':
            if port.in_waiting > 13:
                line = port.readline()
                line = line.decode('UTF-8')
                line = line.strip()
                red, green, blue = str(line).split(':')
                red = int(red)/ADC_RANGE 
                green = int(green)/ADC_RANGE 
                blue = int(blue)/ADC_RANGE 
            else:
                return {'PASS_THROUGH'}
            if self.mode == 'HSV':
                red, green, blue = colorsys.hsv_to_rgb(red, green, blue)
            elif self.mode == 'RGB':
                pass
            bpy.context.object.active_material.diffuse_color = (red, green, blue)
        elif event.type == 'K':
            if self.mode == 'HSV':
                self.mode = 'RGB'
            elif self.mode =='RGB':
                self.mode = 'HSV'
        elif event.type in {'ESC'}:
            print("Closing serial port.")
            port.close()
            return {'FINISHED'}

        return {'PASS_THROUGH'}

    def execute(self, context):
        wm = context.window_manager
        self._timer = wm.event_timer_add(0.05, context.window)
        wm.modal_handler_add(self)
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        wm = context.window_manager
        wm.event_timer_remove(self._timer)


def register():
    bpy.utils.register_class(ModalTimerOperator)


def unregister():
    bpy.utils.unregister_class(ModalTimerOperator)


if __name__ == "__main__":
    register()
    bpy.ops.wm.modal_timer_operator()

Please take into consideration that it has been written for Blender 2.79. Things might have change when it come to the API.

What's most important in this script is handling the communication without blocking the main Blender thread. That has been achieved with the ModalOperator class. By pressing K key on your keyboard you can change between RGB and HSV modes.

The code for the Arduino:

int red_raw   = 0;
int green_raw = 0;
int blue_raw  = 0;

char buf[64];

typedef struct _State_t {
  int red;
  int green;
  int blue;
} State_t;

State_t current;
State_t previous;

#define SAMPLES_COUNT 3

int red_avg[SAMPLES_COUNT];
int green_avg[SAMPLES_COUNT];
int blue_avg[SAMPLES_COUNT];


void setup() {
  Serial.begin(115200);
}

void loop() {
  static int index = 0;
  
  if (index > SAMPLES_COUNT-1) index = 0;
  
  // read the analog in value:
  blue_raw  = analogRead(A0);
  green_raw = analogRead(A1);
  red_raw  = analogRead(A2);
  
  red_avg[index] = red_raw;
  green_avg[index] = green_raw;
  blue_avg[index] = blue_raw;
  index++;
  int red_sum = 0;
  int green_sum = 0;
  int blue_sum = 0;
  for (int i = 0; i < SAMPLES_COUNT; i++) {
    red_sum += red_avg[i];
    green_sum += green_avg[i];
    blue_sum += blue_avg[i];
  }
  red_raw = red_sum/SAMPLES_COUNT;
  green_raw = green_sum/SAMPLES_COUNT;
  blue_raw = blue_sum/SAMPLES_COUNT;
  
  // map it to the range of the analog out:
  //current.blue  = map(blue_raw,  0, 1023, 0, 255);
  //current.green = map(green_raw, 0, 1023, 0, 255);
  //current.red   = map(red_raw,   0, 1023, 0, 255);
  current.red = red_raw;
  current.green = green_raw;
  current.blue = blue_raw;

  if (
    ((abs(current.red-previous.red))     > 2) ||
    ((abs(current.green-previous.green)) > 2) ||
    ((abs(current.blue-previous.blue))   > 2) ||
  )
  {
   if ((current.red != previous.red) || (current.green != previous.green) || (current.blue != previous.blue)) {
     sprintf(buf, "%04d:%04d:%04d\r\n", current.red, current.green, current.blue);
     Serial.print(buf);
    }
  }

  previous = current;
  delay(50);
}

Nothing fancy here. There is a bit of filtering applied to the values read from the ADC. If the values corresponding to the specific potentiometers changed by more than 2, the new values get pushed via the serial port.