Interfacing with Python

Writing your own Python modules that you can use with OpenSDraw.

Step 1

Create the Python file.

#!/usr/bin/env python

import numbers
import os
from PIL import Image

# Define the basestring type for Python 3.
try:
    basestring
except NameError:
    basestring = str

# These OpenSDraw modules have some classes that we will use.
import opensdraw.lcad_language.interpreter as interpreter
import opensdraw.lcad_language.typeFunctions as typeFunctions

# This OpenSDraw module defines some types that we will use.
import opensdraw.lcad_language.lcadTypes as lcadTypes

# This OpenSDraw module defines some exceptions that we will use.
import opensdraw.lcad_language.lcadExceptions as lcadExceptions

# OpenSDraw will look for this dictionary to figure out what functions to import.
lcad_functions = {}

#
# Your function(s) (like all functions in OpenSDraw) should be a sub-class of 
# the interpreter.LCadFunction class.
#
# This class will open a user requested picture and return another class that
# the user can use to access various properties of the picture.
#
class OpenPicture(interpreter.LCadFunction):

    def __init__(self):

        # interpreter.LCadFunction.__init__ takes one argument, the name of the function.
        interpreter.LCadFunction.__init__(self, "picture")

        # Set the function signature so that the interpreter will type check for a single 
        # argument of type string (the name of the picture file). Use basestring since
        # this will also work with unicode strings.
        self.setSignature([[basestring]])

    # 
    # model is an instance of interpreter.Model. This stores the parts, groups,
    #    primitives and etc. This is the first argument to every function.
    #
    # filename is the name of the file.
    #
    # When you call this function the interpreter will check the arguments based on
    # the function signature provided in __init__(). The arguments to call should
    # match those in the signature.
    #
    def call(self, model, filename):

        # Check that the requested picture exists.
        if os.path.exists(filename):
            
            # Return an instance of the Picture class.
            return Picture(Image.open(filename))
    
        # If not, throw an exception.
        else:
            raise lcadExceptions.LCadException("picture " + filename + " not found.")

# Make sure to add an instance of your function to the functions dictionary.
lcad_functions["open-picture"] = OpenPicture()


#
# This class will return either the picture size, or the color at a particular
# pixel depending on the arguments that the user supplies
#
class Picture(interpreter.LCadFunction):

    def __init__(self, im):
        interpreter.LCadFunction.__init__(self, "user created picture function")

        # Store the PIL Image object.
        self.im = im

        # Set signature to be exactly two arguments both of which are numbers
        # or the symbols t/nil.
        self.setSignature([[numbers.Number, lcadTypes.LCadBoolean], [numbers.Number, lcadTypes.LCadBoolean]])

    def call(self, model, x, y):

        # If we got t/nil return the size of the picture.
        # (Note: To check for Truth use 'functions.isTrue(val)').
        if typeFunctions.isBoolean(x) or typeFunctions.isBoolean(y):
            return list(self.im.size)

        # Otherwise return the color of the pixel as a LDraw "direct" color. Best
        # practice might be to do some range checking. This will also fail for
        # certain types of images (such as .gif).
        [r, g, b] = self.im.getpixel((x, y))

        # Convert colors (0-255) to upper case hex & concatenate.
        return "0x2" + "".join(map(lambda x: "{0:#0{1}x}".format(x,4).upper()[2:], [r, g, b]))

Note

This is the picture.py file in the examples folder.

Step 2

Create the lcad file.


(import locate :local)

; Import of picture.py module.
(pyimport picture)

; Load the example picture.
(def pic (open-picture "9393.png"))

; Get the picture size.
(def size (pic t t))
(def size-x (aref size 0))
(def size-y (aref size 1))

; Create picture backing. This works best for pictures whose size 
; is a multiple of 2. Also, some of the corners can end up as 2 x 2 
; on top of 2 x 2 which would not actually work.
(def stripe (y offset length)
 (block
  (def pos offset)
  (if (= (% offset 2) 0)
    (block
     (sb pos y 0 90 0 0 "3022" 7)
     (set pos (+ pos 3)))
   (block
    (sb pos y 0 90 0 0 "3020" 7)
    (set pos (+ pos 4))))
  (while (< pos (- length 2))
   (sb pos y 0 90 0 0 "3020" 7)
   (set pos (+ pos 4)))
  (if (< pos length)
    (sb (- pos 1) y 0 90 0 0 "3022" 7))))

; Backing upper layer.
(for (y (/ (+ size-y 2) 2))
 (translate (list (bw -0.5) (bw -0.5) (bh 0.3))
  (stripe (* 2 y) (% (+ y 1) 2) (+ size-x 2))))

; Backing lower layer.
(for (x (/ (+ size-x 2) 2))
 (translate (list (bw (- size-x 0.5)) (bw -0.5) (bh 0.6))
  (rotate (list 0 0 90)
   (stripe (* 2 x) (% x 2) (+ size-y 2)))))

; re-create the picture using Plate 1 x 1 Round.
(for (i size-x)
 (for (j size-y)
  (sb i j 0 90 0 0 "4073" (pic i j))))

Note

This is the picture.lcad file in the examples folder.

Step 3

Convert the .lcad file to a .mpd file using lcad_to_ldraw.py.

cd opensdraw/opensdraw/examples
python ../scripts/lcad_to_ldraw.py picture.lcad

Note

Large pictures can easily overwhelm both OpenSDraw and LDView. Picture size less than 100 x 100 or so are probably the best.

Also

If you want your module to always be available you can add it to the lcad_language/modules.xml file.