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.