Esempio n. 1
0
def generateBitmap(operation, orientation, regionmap, z):
	"""
		generate bitmap for an operation's z-height level
	"""
	global scale
	length = len(regionmap)
	width = len(regionmap[0])
	bitmap = [[False for x in range(width) ]for y in range(length)]
	operationList = [int(region) for region in operation["regionlist"].split(" ") if region != ""]


	for x in range(length):
		for y in range(width):
			if regionmap[x][y] in operationList:
				if toBeMachined(orientation, x, y, z):
					bitmap[x][y] = [0]     #shoundnt be cut
				else:
					bitmap[x][y] = [1] #should be cut
			else:
				bitmap[x][y] = [1]
			# print int(bitmap[x][y][0]),
		# print "-"
	# print "---------------------"
	img = Image(length, width, scale, channels=1, depth=8)
	img.array = np.array(bitmap, dtype=np.uint8)

	return img
Esempio n. 2
0
def generate_bitmap(operation, orientation, regionmap, z):
    """
        generate bitmap for an operation's z-height level
    """
    global SCALE
    length = len(regionmap)
    width = len(regionmap[0])
    bitmap = [[False for x in range(width)]for y in range(length)]
    operationList = [int(region) for region in operation["regionlist"].split(" ") if region != ""]


    for x in range(length):
        for y in range(width):
            if regionmap[x][y] in operationList:
                if to_be_machined(orientation, x, y, z):
                    bitmap[x][y] = [0]     #shoundnt be cut
                else:
                    bitmap[x][y] = [1] #should be cut
            else:
                bitmap[x][y] = [1]
            # print int(bitmap[x][y][0]),
        # print "-"
    # print "---------------------"
    img = Image(length, width, SCALE, channels=1, depth=8)
    img.array = np.array(bitmap, dtype=np.uint8)

    return img
Esempio n. 3
0
    def render_multi(self,
                     region=None,
                     threads=8,
                     alpha=0,
                     beta=0,
                     resolution=10):
        """ @brief Renders to an image
            @param region Render region (default bounding box)
            @param threads Threads to use (default 8)
            @param alpha Rotation about Z axis (default 0)
            @param beta Rotation about X axis (default 0)
            @resolution Resolution in voxels per mm
            @returns A tuple with a height-map, shaded image, and image with colored normals
        """
        if region is None:
            region = self.bounding_region(resolution, alpha, beta)

        depth = Image(
            region.ni,
            region.nj,
            channels=1,
            depth=16,
        )
        shaded = Image(
            region.ni,
            region.nj,
            channels=1,
            depth=16,
        )
        normals = Image(
            region.ni,
            region.nj,
            channels=3,
            depth=8,
        )

        subregions = region.split_xy(threads)

        M = (ctypes.c_float * 4)(cos(radians(alpha)), sin(radians(alpha)),
                                 cos(radians(beta)), sin(radians(beta)))
        args = [(self.ptr, s, M, depth.pixels, shaded.pixels, normals.pixels)
                for s in subregions]
        multithread(libfab.render_asdf_shaded, args)

        for image in [depth, shaded, normals]:
            image.xmin = region.X[0]
            image.xmax = region.X[region.ni]
            image.ymin = region.Y[0]
            image.ymax = region.Y[region.nj]
            image.zmin = region.Z[0]
            image.zmax = region.Z[region.nk]
        return depth, shaded, normals
Esempio n. 4
0
def generate_gcode(*args):
    """
        generate gcode for heightmap in a given orientation
        note: [toBeImproved]heightmap passed as formatted string isnt a
        good way to structure the code
    """
    arg = args[0][0]
    orientation = args[0][1]
    global diameter, step, feed
    command = ["./scripts/operationPlanner"]
    p = Popen(command, stdout=PIPE, stdin=PIPE, stderr=STDOUT)
    generatorOutput = p.communicate(input=arg)[0]
    plan = json.loads(generatorOutput)

    length, width, zmax, scale, array = get_heightmap(arg)
    values = {
        'diameter':    diameter,
        'offsets':     -1,
        'overlap':     0.5,
        'step':        step,
        'feed': feed,
        'plunge': 2.5,
        'jog': 2,
        'zmax' : zmax,
        'zmin' : 0
        }

    img = Image(length, width, scale, zmax=zmax, array=array,
                provides_array=True, channels=1, depth=16)
    #getting paths
    paths = run_rough(img, values, plan, orientation)
    #generating gcode from paths
    gcode = generate_gcode_from_path(paths, values["feed"], values["jog"], values["plunge"])
    return gcode
Esempio n. 5
0
    def __init__(self, parent):
        FabPanel.__init__(self, parent)
        self.construct('Lattice', [
            ('Resolution (pixels/mm)\n', 'res', float, lambda f: f > 0)])

        self.res.Bind(wx.EVT_TEXT, self.parent.update)
        self.img = Image(0,0)
Esempio n. 6
0
    def load(self):
        """ @brief Loads the current design file
            @details The file is defined by self.directory and self.filename
        """
        self.clear()

        path = os.path.join(self.directory, self.filename)

        if path[-4:] == '.cad':
            with open(path, 'r') as f:
                text = f.read()
                if text.split('\n')[0] == '##    Geometry header    ##':
                    koko.PRIMS.reconstruct(eval(text.split('\n')[1]))
                    koko.PRIMS.undo_stack = [koko.PRIMS.reconstructor()]
                    text = '\n'.join(text.split('\n')[3:])
            koko.EDITOR.text = text
            koko.FRAME.status = 'Loaded .cad file'

            self.mode = 'cad/cam'
            self.first_render = True
            self.savepoint(True)

        elif path[-4:] == '.png':
            self.mode = 'cam'
            img = Image.load(path)
            koko.CANVAS.load_image(img)
            koko.GLCANVAS.load_image(img)
            koko.FAB.set_input(img)
            wx.CallAfter(self.snap_bounds)

        elif path[-4:] == '.stl':
            self.mode = 'cam'
            mesh = Mesh.load(path)
            koko.GLCANVAS.load_mesh(mesh)
            wx.CallAfter(self.snap_bounds)

        elif path[-5:] == '.asdf':
            self.render_mode('3D')
            self.mode = 'cam'

            msg = dialogs.display_message('Loading...', 'Loading ASDF.')
            msg.Raise()
            wx.Yield()

            asdf = ASDF.load(path)
            msg.txt.SetLabel('Triangulating')
            wx.Yield()

            mesh = asdf.triangulate()
            mesh.source = Struct(type=ASDF, file=path, depth=0)
            msg.Destroy()

            koko.GLCANVAS.load_mesh(mesh)
            koko.FAB.set_input(asdf)
Esempio n. 7
0
    def render_distance(self, resolution=10):
        """ @brief Draws the ASDF as a distance field (used for debugging)
            @param resolution Image resolution
            @returns An 16-bit, 1-channel Image showing the distance field
        """
        region = self.bounding_region(resolution)
        image = Image(region.ni, region.nj, channels=1, depth=16)
        image.xmin = region.X[0]
        image.xmax = region.X[region.ni]
        image.ymin = region.Y[0]
        image.ymax = region.Y[region.nj]
        image.zmin = region.Z[0]
        image.zmax = region.Z[region.nk]

        minimum = libfab.asdf_get_min(self.ptr)
        maximum = libfab.asdf_get_max(self.ptr)

        libfab.draw_asdf_distance(self.ptr, region, minimum, maximum,
                                  image.pixels)
        return image
Esempio n. 8
0
    def export_png(self):
        ''' Exports a png using libtree.
        '''

        if self.make_heightmap:
            out = self.make_image(self.cad.shape)
        else:
            i = 0
            imgs = []
            for e in self.cad.shapes:
                if self.event.is_set(): return
                img = self.make_image(e)
                if img is not None: imgs.append(img)
                i += 1
                self.window.progress = i*90/len(self.cad.shapes)
            out = Image.merge(imgs)

        if self.event.is_set(): return

        self.window.progress = 90
        out.save(self.filename)
        self.window.progress = 100
Esempio n. 9
0
    def export_png(self):
        ''' Exports a png using libtree.
        '''

        if self.make_heightmap:
            out = self.make_image(self.cad.shape)
        else:
            i = 0
            imgs = []
            for e in self.cad.shapes:
                if self.event.is_set(): return
                img = self.make_image(e)
                if img is not None: imgs.append(img)
                i += 1
                self.window.progress = i * 90 / len(self.cad.shapes)
            out = Image.merge(imgs)

        if self.event.is_set(): return

        self.window.progress = 90
        out.save(self.filename)
        self.window.progress = 100
Esempio n. 10
0
    def render_distance(self, resolution=10):
        """ @brief Draws the ASDF as a distance field (used for debugging)
            @param resolution Image resolution
            @returns An 16-bit, 1-channel Image showing the distance field
        """
        region = self.bounding_region(resolution)
        image = Image(region.ni, region.nj, channels=1, depth=16)
        image.xmin = region.X[0]
        image.xmax = region.X[region.ni]
        image.ymin = region.Y[0]
        image.ymax = region.Y[region.nj]
        image.zmin = region.Z[0]
        image.zmax = region.Z[region.nk]

        minimum = libfab.asdf_get_min(self.ptr)
        maximum = libfab.asdf_get_max(self.ptr)

        libfab.draw_asdf_distance(
            self.ptr, region, minimum, maximum, image.pixels
        )
        return image
Esempio n. 11
0
    def load(self):
        """ @brief Loads the current design file
            @details The file is defined by self.directory and self.filename
        """
        self.clear()

        path = os.path.join(self.directory, self.filename)

        if path[-4:] == '.cad':
            with open(path, 'r') as f:
                text = f.read()
                if text.split('\n')[0] == '##    Geometry header    ##':
                    koko.PRIMS.reconstruct(eval(text.split('\n')[1]))
                    koko.PRIMS.undo_stack = [koko.PRIMS.reconstructor()]
                    text = '\n'.join(text.split('\n')[3:])
            koko.EDITOR.text = text
            koko.FRAME.status = 'Loaded .cad file'

            self.mode = 'cad'
            self.first_render = True
            self.savepoint(True)

        elif path[-4:] == '.png':
            self.mode = 'png'
            img = Image.load(path)
            koko.CANVAS.load_image(img)
            koko.GLCANVAS.load_image(img)
            koko.FAB.set_input(img)
            wx.CallAfter(self.snap_bounds)

        elif path[-4:] == '.stl':
            self.mode = 'stl'
            mesh = Mesh.load(path)

            koko.FRAME.get_menu('View', '3D').Check(True)
            self.render_mode('3D')

            koko.GLCANVAS.load_mesh(mesh)
            wx.CallAfter(self.snap_bounds)

        elif path[-5:] == '.asdf':
            self.mode = 'asdf'

            koko.FRAME.status = 'Loading ASDF'
            wx.Yield()

            asdf = ASDF.load(path)
            koko.FRAME.status = 'Triangulating'
            wx.Yield()

            mesh = asdf.triangulate()
            mesh.source = Struct(type=ASDF, file=path, depth=0)
            koko.FRAME.status = ''

            koko.FRAME.get_menu('View', '3D').Check(True)
            self.render_mode('3D')

            koko.GLCANVAS.load_mesh(mesh)
            koko.FAB.set_input(asdf)

        elif path[-4:] == '.vol':
            self.mode = 'vol'
            koko.IMPORT.set_target(self.directory, self.filename)
Esempio n. 12
0
class MathTree(object):
    """ @class MathTree
        @brief Represents a distance metric math expression.

        @details
        Arithmetic operators are overloaded to extend the tree with
        either distance metric arithmetic or shape logical expressions,
        depending on the value of the instance variable 'shape'
    """
    def __init__(self, math, shape=False, color=None):
        """ @brief MathTree constructor
            @param math Math string (in prefix notation)
            @param shape Boolean modifying arithmetic operators
            @param color Color tuple or None
        """

        ## @var math
        # Math string (in sparse prefix syntax)
        if type(math) in [int, float]:
            self.math = 'f' + str(math)
        else:
            self.math = math

        ## @var shape
        # Boolean modify the behavior of arithmetic operators
        self.shape = shape

        ## @var color
        # Assigned color, or None
        self.color = color

        self._str = None
        self._ptr = None

        ## @var bounds
        # X, Y, Z bounds (or None)
        self.bounds = [None] * 6

        self.lock = threading.Lock()

    @threadsafe
    def __del__(self):
        """ @brief MathTree destructor """
        if self._ptr is not None and libfab is not None:
            libfab.free_tree(self.ptr)

    @property
    def ptr(self):
        """ @brief Parses self.math and returns a pointer to a MathTree structure
        """
        if self._ptr is None:
            self._ptr = libfab.parse(self.math)
        return self._ptr

    ############################################################################

    @property
    def dx(self):
        try:
            return self.xmax - self.xmin
        except TypeError:
            return None

    @property
    def dy(self):
        try:
            return self.ymax - self.ymin
        except TypeError:
            return None

    @property
    def dz(self):
        try:
            return self.zmax - self.zmin
        except TypeError:
            return None

    @property
    def bounds(self):
        return [
            self.xmin, self.xmax, self.ymin, self.ymax, self.zmin, self.zmax
        ]

    @bounds.setter
    def bounds(self, value):
        for b in ['xmin', 'xmax', 'ymin', 'ymax', 'zmin', 'zmax']:
            setattr(self, b, value.pop(0))

    @property
    def xmin(self):
        return self._xmin

    @xmin.setter
    def xmin(self, value):
        if value is None: self._xmin = None
        else:
            try:
                self._xmin = float(value)
            except:
                raise ValueError('xmin must be a float')

    @property
    def xmax(self):
        return self._xmax

    @xmax.setter
    def xmax(self, value):
        if value is None: self._xmax = None
        else:
            try:
                self._xmax = float(value)
            except:
                raise ValueError('xmax must be a float')

    @property
    def ymin(self):
        return self._ymin

    @ymin.setter
    def ymin(self, value):
        if value is None: self._ymin = None
        else:
            try:
                self._ymin = float(value)
            except:
                raise ValueError('ymin must be a float')

    @property
    def ymax(self):
        return self._ymax

    @ymax.setter
    def ymax(self, value):
        if value is None: self._ymax = None
        else:
            try:
                self._ymax = float(value)
            except:
                raise ValueError('ymax must be a float')

    @property
    def zmin(self):
        return self._zmin

    @zmin.setter
    def zmin(self, value):
        if value is None: self._zmin = None
        else:
            try:
                self._zmin = float(value)
            except:
                raise ValueError('zmin must be a float')

    @property
    def zmax(self):
        return self._zmax

    @zmax.setter
    def zmax(self, value):
        if value is None: self._zmax = None
        else:
            try:
                self._zmax = float(value)
            except:
                raise ValueError('zmax must be a float')

    @property
    def bounded(self):
        return all(d is not None for d in [self.dx, self.dy, self.dz])

    ############################################################################

    @property
    def color(self):
        return self._color

    @color.setter
    def color(self, rgb):
        named = {
            'red': (255, 0, 0),
            'blue': (0, 0, 255),
            'green': (0, 255, 0),
            'white': (255, 255, 255),
            'grey': (128, 128, 128),
            'black': (0, 0, 0),
            'yellow': (255, 255, 0),
            'cyan': (0, 255, 255),
            'magenta': (255, 0, 255),
            'teal': (0, 255, 255),
            'pink': (255, 0, 255),
            'brown': (145, 82, 45),
            'tan': (125, 90, 60),
            'navy': (0, 0, 128)
        }
        if type(rgb) is str and rgb in named:
            self._color = named[rgb]
        elif type(rgb) in [tuple, list] and len(rgb) == 3:
            self._color = tuple(rgb)
        elif rgb is None:
            self._color = rgb
        else:
            raise ValueError(
                'Invalid color (must be integer 3-value tuple or keyword)')

    ############################################################################

    @staticmethod
    def wrap(value):
        ''' Converts a value to a MathTree.

            None values are left alone,
            Strings are assumed to be valid math strings and wrapped
            Floats / ints are converted'''
        if isinstance(value, MathTree):
            return value
        elif value is None:
            return value
        elif type(value) is str:
            return MathTree(value)
        elif type(value) is not float:
            try:
                value = float(value)
            except (ValueError, TypeError):
                raise TypeError('Wrong type for MathTree arithmetic (%s)' %
                                type(value))
        return MathTree.Constant(value)

    @classmethod
    @forcetree
    def min(cls, A, B):
        return cls('i' + A.math + B.math)

    @classmethod
    @forcetree
    def max(cls, A, B):
        return cls('a' + A.math + B.math)

    @classmethod
    @forcetree
    def pow(cls, A, B):
        return cls('p' + A.math + B.math)

    @classmethod
    @forcetree
    def sqrt(cls, A):
        return cls('r' + A.math)

    @classmethod
    @forcetree
    def abs(cls, A):
        return cls('b' + A.math)

    @classmethod
    @forcetree
    def square(cls, A):
        return cls('q' + A.math)

    @classmethod
    @forcetree
    def sin(cls, A):
        return cls('s' + A.math)

    @classmethod
    @forcetree
    def cos(cls, A):
        return cls('c' + A.math)

    @classmethod
    @forcetree
    def tan(cls, A):
        return cls('t' + A.math)

    @classmethod
    @forcetree
    def asin(cls, A):
        return cls('S' + A.math)

    @classmethod
    @forcetree
    def acos(cls, A):
        return cls('C' + A.math)

    @classmethod
    @forcetree
    def atan(cls, A):
        return cls('T' + A.math)

    #########################
    #  MathTree Arithmetic  #
    #########################

    # If shape is set, then + and - perform logical combination;
    # otherwise, they perform arithmeic.
    @matching
    @forcetree
    def __add__(self, rhs):
        if self.shape or (rhs and rhs.shape):

            if rhs is None: return self.clone()

            t = MathTree('i' + self.math + rhs.math, True)

            if self.dx is not None and rhs.dx is not None:
                t.xmin = min(self.xmin, rhs.xmin)
                t.xmax = max(self.xmax, rhs.xmax)
            if self.dx is not None and rhs.dy is not None:
                t.ymin = min(self.ymin, rhs.ymin)
                t.ymax = max(self.ymax, rhs.ymax)
            if self.dz is not None and rhs.dz is not None:
                t.zmin = min(self.zmin, rhs.zmin)
                t.zmax = max(self.zmax, rhs.zmax)

            return t
        else:
            return MathTree('+' + self.math + rhs.math)

    @matching
    @forcetree
    def __radd__(self, lhs):
        if lhs is None: return self.clone()

        if self.shape or (lhs and lhs.shape):

            t = MathTree('i' + lhs.math + self.math)
            if self.dx is not None and lhs.dx is not None:
                t.xmin = min(self.xmin, lhs.xmin)
                t.xmax = max(self.xmax, lhs.xmax)
            if self.dy is not None and lhs.dy is not None:
                t.ymin = min(self.ymin, lhs.ymin)
                t.ymax = max(self.ymax, lhs.ymax)
            if self.dz is not None and lhs.dz is not None:
                t.zmin = min(self.zmin, lhs.zmin)
                t.zmax = max(self.zmax, lhs.zmax)
            return t
        else:
            return MathTree('+' + lhs.math + self.math)

    @matching
    @forcetree
    def __sub__(self, rhs):
        if self.shape or (rhs and rhs.shape):

            if rhs is None: return self.clone()

            t = MathTree('a' + self.math + 'n' + rhs.math, True)
            for i in ['xmin', 'xmax', 'ymin', 'ymax', 'zmin', 'zmax']:
                setattr(t, i, getattr(self, i))
            return t
        else:
            return MathTree('-' + self.math + rhs.math)

    @matching
    @forcetree
    def __rsub__(self, lhs):
        if self.shape or (lhs and lhs.shape):

            if lhs is None: return MathTree('n' + self.math)

            t = MathTree('a' + lhs.math + 'n' + self.math, True)
            for i in ['xmin', 'xmax', 'ymin', 'ymax', 'zmin', 'zmax']:
                setattr(t, i, getattr(lhs, i))
            return t
        else:
            return MathTree('-' + lhs.math + self.math)

    @matching
    @forcetree
    def __and__(self, rhs):
        if self.shape or rhs.shape:
            t = MathTree('a' + self.math + rhs.math, True)
            if self.dx is not None and rhs.dx is not None:
                t.xmin = max(self.xmin, rhs.xmin)
                t.xmax = min(self.xmax, rhs.xmax)
            if self.dy is not None and rhs.dy is not None:
                t.ymin = max(self.ymin, rhs.ymin)
                t.ymax = min(self.ymax, rhs.ymax)
            if self.dz is not None and rhs.dz is not None:
                t.zmin = max(self.zmin, rhs.zmin)
                t.zmax = min(self.zmax, rhs.zmax)
            return t
        else:
            raise NotImplementedError(
                '& operator is undefined for non-shape math expressions.')

    @matching
    @forcetree
    def __rand__(self, lhs):
        if self.shape or lhs.shape:
            t = MathTree('a' + lhs.math + self.math, True)
            if self.dx is not None and lhs.dx is not None:
                t.xmin = max(self.xmin, lhs.xmin)
                t.xmax = min(self.xmax, lhs.xmax)
            if self.dy is not None and lhs.dy is not None:
                t.ymin = max(self.ymin, lhs.ymin)
                t.ymax = min(self.ymax, lhs.ymax)
            if self.dz is not None and lhs.dz is not None:
                t.zmin = max(self.zmin, lhs.zmin)
                t.zmax = min(self.zmax, lhs.zmax)
            return t
        else:
            raise NotImplementedError(
                '& operator is undefined for non-shape math expressions.')

    @matching
    @forcetree
    def __or__(self, rhs):
        if self.shape or rhs.shape:
            t = MathTree('i' + self.math + rhs.math, True)
            if self.dx is not None and rhs.dx is not None:
                t.xmin = min(self.xmin, rhs.xmin)
                t.xmax = max(self.xmax, rhs.xmax)
            if self.dy is not None and rhs.dy is not None:
                t.ymin = min(self.ymin, rhs.ymin)
                t.ymax = max(self.ymax, rhs.ymax)
            if self.dz is not None and rhs.dz is not None:
                t.zmin = min(self.zmin, rhs.zmin)
                t.zmax = max(self.zmax, rhs.zmax)
            return t
        else:
            raise NotImplementedError(
                '| operator is undefined for non-shape math expressions.')

    @matching
    @forcetree
    def __ror__(self, lhs):
        if self.shape or lhs.shape:
            t = MathTree('i' + lhs.math + self.math, True)
            if self.dx is not None and lhs.dx is not None:
                t.xmin = min(self.xmin, lhs.xmin)
                t.xmax = max(self.xmax, lhs.xmax)
            if self.dy is not None and lhs.dy is not None:
                t.ymin = min(self.ymin, lhs.ymin)
                t.ymax = max(self.ymax, lhs.ymax)
            if self.dz is not None and lhs.dz is not None:
                t.zmin = min(self.zmin, lhs.zmin)
                t.zmax = max(self.zmax, lhs.zmax)
            return t
        else:
            raise NotImplementedError(
                '| operator is undefined for non-shape math expressions.')

    @forcetree
    def __mul__(self, rhs):
        return MathTree('*' + self.math + rhs.math)

    @forcetree
    def __rmul__(self, lhs):
        return MathTree('*' + lhs.math + self.math)

    @forcetree
    def __div__(self, rhs):
        return MathTree('/' + self.math + rhs.math)

    @forcetree
    def __rdiv__(self, lhs):
        return MathTree('/' + lhs.math + self.math)

    @forcetree
    def __neg__(self):
        return MathTree('n' + self.math, shape=self.shape)

    ###############################
    ## String and representation ##
    ###############################

    def __str__(self):
        if self._str is None:
            self._str = self.make_str()
        return self._str

    def make_str(self, verbose=False):
        """ @brief Converts the object into an infix-notation string

            @details
            Creates a OS pipe, instructs the object to print itself into the pipe, and reads the output in chunks of maximum size 1024.
        """

        # Create a pipe to get the printout
        read, write = os.pipe()

        # Start the print function running in a separate thread
        # (so that we can eat the output and avoid filling the pipe)
        if verbose: printer = libfab.fdprint_tree_verbose
        else: printer = libfab.fdprint_tree
        t = threading.Thread(target=printer, args=(self.ptr, write))
        t.daemon = True
        t.start()

        s = r = os.read(read, 1024)
        while r:
            r = os.read(read, 1024)
            s += r
        t.join()

        os.close(read)

        return s

    def __repr__(self):
        return "'%s' (tree at %s)" % (self, hex(self.ptr.value))

    def verbose(self):
        return self.make_str(verbose=True)

    def save_dot(self, filename, arrays=False):
        """ @brief Converts math expression to .dot graph description
        """
        if arrays:
            libfab.dot_arrays(self.ptr, filename)
        else:
            libfab.dot_tree(self.ptr, filename)

    @property
    def node_count(self):
        return libfab.count_nodes(self.ptr)

    #################################
    ## Tree manipulation functions ##
    #################################
    @forcetree
    def map(self, X=None, Y=None, Z=None):
        """ @brief Applies a map operator to a tree
            @param X New X function or None
            @param Y New Y function or None
            @param Z New Z function or None
        """
        return MathTree('m' + (X.math if X else ' ') + (Y.math if Y else ' ') +
                        (Z.math if Z else ' ') + self.math,
                        shape=self.shape,
                        color=self.color)

    @forcetree
    def map_bounds(self, X=None, Y=None, Z=None):
        """ @brief Calculates remapped bounds
            @returns Array of remapped bounds
            @param X New X function or None
            @param Y New Y function or None
            @param Z New Z function or None
            @details Note that X, Y, and Z should be the inverse
            of a coordinate mapping to properly transform bounds.
        """
        if self.dx is not None: x = Interval(self.xmin, self.xmax)
        else: x = Interval(float('nan'))
        if self.dy is not None: y = Interval(self.ymin, self.ymax)
        else: y = Interval(float('nan'))
        if self.dz is not None: z = Interval(self.zmin, self.zmax)
        else: z = Interval(float('nan'))

        if self.dx is not None: a = Interval(self.xmin, self.xmax)
        else: a = Interval(float('nan'))
        if self.dy is not None: b = Interval(self.ymin, self.ymax)
        else: b = Interval(float('nan'))
        if self.dz is not None: c = Interval(self.zmin, self.zmax)
        else: c = Interval(float('nan'))

        if X:
            X_p = libfab.make_packed(X.ptr)
            a = libfab.eval_i(X_p, x, y, z)
            libfab.free_packed(X_p)

        if Y:
            Y_p = libfab.make_packed(Y.ptr)
            b = libfab.eval_i(Y_p, x, y, z)
            libfab.free_packed(Y_p)

        if Z:
            Z_p = libfab.make_packed(Z.ptr)
            c = libfab.eval_i(Z_p, x, y, z)
            libfab.free_packed(Z_p)

        bounds = []
        for i in [a, b, c]:
            if math.isnan(i.lower) or math.isnan(i.upper):
                bounds += [None, None]
            else:
                bounds += [i.lower, i.upper]

        return bounds

    @threadsafe
    def clone(self):
        m = MathTree(self.math, shape=self.shape, color=self.color)
        m.bounds = [b for b in self.bounds]
        if self._ptr is not None:
            m._ptr = libfab.clone_tree(self._ptr)
        return m

    #################################
    #    Rendering functions        #
    #################################

    def render(self,
               region=None,
               resolution=None,
               mm_per_unit=None,
               threads=8,
               interrupt=None):
        """ @brief Renders a math tree into an Image
            @param region Evaluation region (if None, taken from expression bounds)
            @param resolution Render resolution in voxels/unit
            @param mm_per_unit Real-world scale
            @param threads Number of threads to use
            @param interrupt threading.Event that aborts rendering if set
            @returns Image data structure
        """

        if region is None:
            if self.dx is None or self.dy is None:
                raise Exception('Unknown render region!')
            elif resolution is None:
                raise Exception('Region or resolution must be provided!')
            region = Region(
                (self.xmin, self.ymin, self.zmin if self.zmin else 0),
                (self.xmax, self.ymax, self.zmax if self.zmax else 0),
                resolution)

        try:
            float(mm_per_unit)
        except ValueError, TypeError:
            raise ValueError('mm_per_unit must be a number')

        if interrupt is None: interrupt = threading.Event()
        halt = ctypes.c_int(0)  # flag to abort render
        image = Image(
            region.ni,
            region.nj,
            channels=1,
            depth=16,
        )

        # Divide the task to share among multiple threads
        clones = [self.clone() for i in range(threads)]
        packed = [libfab.make_packed(c.ptr) for c in clones]

        subregions = region.split_xy(threads)

        # Solve each region in a separate thread
        args = zip(packed, subregions, [image.pixels] * threads,
                   [halt] * threads)

        multithread(libfab.render16, args, interrupt, halt)

        for p in packed:
            libfab.free_packed(p)

        image.xmin = region.X[0] * mm_per_unit
        image.xmax = region.X[region.ni] * mm_per_unit
        image.ymin = region.Y[0] * mm_per_unit
        image.ymax = region.Y[region.nj] * mm_per_unit
        image.zmin = region.Z[0] * mm_per_unit
        image.zmax = region.Z[region.nk] * mm_per_unit

        return image
Esempio n. 13
0
    def load(self):
        """ @brief Loads the current design file
            @details The file is defined by self.directory and self.filename
        """
        self.clear()

        path = os.path.join(self.directory, self.filename)

        if path[-3:] == '.ko' or path[-4:] == '.cad':
            with open(path, 'r') as f:
                text = f.read()
                if text.split('\n')[0] == '##    Geometry header    ##':
                    koko.PRIMS.reconstruct(eval(text.split('\n')[1]))
                    koko.PRIMS.undo_stack = [koko.PRIMS.reconstructor()]
                    text = '\n'.join(text.split('\n')[3:])
            koko.EDITOR.text = text
            koko.FRAME.status = 'Loaded design file'

            if path[-4:] == '.cad':
                dialogs.warning("""
This file has a '.cad' extension, which was superceded by '.ko'.  It may use deprecated features or syntax.

If it is an example file, the 'kokopelli/examples' folder may include an updated version with a '.ko' extension"""
                                )

            self.mode = 'cad'
            self.first_render = True
            self.savepoint(True)

        elif path[-4:] == '.png':
            self.mode = 'png'
            img = Image.load(path)
            koko.CANVAS.load_image(img)
            koko.GLCANVAS.load_image(img)
            koko.FAB.set_input(img)
            wx.CallAfter(self.snap_bounds)

        elif path[-4:] == '.stl':
            self.mode = 'stl'
            mesh = Mesh.load(path)

            koko.FRAME.get_menu('View', '3D').Check(True)
            self.render_mode('3D')

            koko.GLCANVAS.load_mesh(mesh)
            wx.CallAfter(self.snap_bounds)

        elif path[-5:] == '.asdf':
            self.mode = 'asdf'

            koko.FRAME.status = 'Loading ASDF'
            wx.Yield()

            asdf = ASDF.load(path)
            koko.FRAME.status = 'Triangulating'
            wx.Yield()

            mesh = asdf.triangulate()
            mesh.source = Struct(type=ASDF, file=path, depth=0)
            koko.FRAME.status = ''

            koko.FRAME.get_menu('View', '3D').Check(True)
            self.render_mode('3D')

            koko.GLCANVAS.load_mesh(mesh)
            koko.FAB.set_input(asdf)

        elif path[-4:] == '.vol':
            self.mode = 'vol'
            koko.IMPORT.set_target(self.directory, self.filename)
Esempio n. 14
0
    def load(self):
        """ @brief Loads the current design file
            @details The file is defined by self.directory and self.filename
        """
        self.clear()

        path = os.path.join(self.directory, self.filename)

        if path[-4:] == '.cad':
            with open(path, 'r') as f:
                text = f.read()
                if text.split('\n')[0] == '##    Geometry header    ##':
                    koko.PRIMS.reconstruct(eval(text.split('\n')[1]))
                    koko.PRIMS.undo_stack = [koko.PRIMS.reconstructor()]
                    text = '\n'.join(text.split('\n')[3:])
            koko.EDITOR.text = text
            koko.FRAME.status = 'Loaded .cad file'

            self.mode = 'cad'
            self.first_render = True
            self.savepoint(True)

        elif path[-4:] == '.png':
            self.mode = 'png'
            img = Image.load(path)
            koko.CANVAS.load_image(img)
            koko.GLCANVAS.load_image(img)
            koko.FAB.set_input(img)
            wx.CallAfter(self.snap_bounds)

        elif path[-4:] == '.stl':
            self.mode = 'stl'
            mesh = Mesh.load(path)

            koko.FRAME.get_menu('View', '3D').Check(True)
            self.render_mode('3D')

            koko.GLCANVAS.load_mesh(mesh)
            wx.CallAfter(self.snap_bounds)

        elif path[-5:] == '.asdf':
            self.mode = 'asdf'

            koko.FRAME.status = 'Loading ASDF'
            wx.Yield()

            asdf = ASDF.load(path)
            koko.FRAME.status = 'Triangulating'
            wx.Yield()

            mesh = asdf.triangulate()
            mesh.source = Struct(type=ASDF, file=path, depth=0)
            koko.FRAME.status = ''

            koko.FRAME.get_menu('View', '3D').Check(True)
            self.render_mode('3D')

            koko.GLCANVAS.load_mesh(mesh)
            koko.FAB.set_input(asdf)

        elif path[-4:] == '.vol':
            self.mode = 'vol'
            koko.IMPORT.set_target(self.directory, self.filename)
Esempio n. 15
0
    def load(self):
        """ @brief Loads the current design file
            @details The file is defined by self.directory and self.filename
        """
        self.clear()

        path = os.path.join(self.directory, self.filename)

        if path[-3:] == '.ko' or path[-4:] == '.cad':
            with open(path, 'r') as f:
                text = f.read()
                if text.split('\n')[0] == '##    Geometry header    ##':
                    koko.PRIMS.reconstruct(eval(text.split('\n')[1]))
                    koko.PRIMS.undo_stack = [koko.PRIMS.reconstructor()]
                    text = '\n'.join(text.split('\n')[3:])
            koko.EDITOR.text = text
            koko.FRAME.status = 'Loaded design file'

            if path[-4:] == '.cad':
                dialogs.warning("""
This file has a '.cad' extension, which was superceded by '.ko'.  It may use deprecated features or syntax.

If it is an example file, the 'kokopelli/examples' folder may include an updated version with a '.ko' extension""")

            self.mode = 'cad'
            self.first_render = True
            self.savepoint(True)

        elif path[-4:] == '.png':
            self.mode = 'png'
            img = Image.load(path)
            koko.CANVAS.load_image(img)
            koko.GLCANVAS.load_image(img)
            koko.FAB.set_input(img)
            wx.CallAfter(self.snap_bounds)

        elif path[-4:] == '.stl':
            self.mode = 'stl'
            mesh = Mesh.load(path)

            koko.FRAME.get_menu('View', '3D').Check(True)
            self.render_mode('3D')

            koko.GLCANVAS.load_mesh(mesh)
            wx.CallAfter(self.snap_bounds)

        elif path[-5:] == '.asdf':
            self.mode = 'asdf'

            koko.FRAME.status = 'Loading ASDF'
            wx.Yield()

            asdf = ASDF.load(path)
            koko.FRAME.status = 'Triangulating'
            wx.Yield()

            mesh = asdf.triangulate()
            mesh.source = Struct(type=ASDF, file=path, depth=0)
            koko.FRAME.status = ''

            koko.FRAME.get_menu('View', '3D').Check(True)
            self.render_mode('3D')

            koko.GLCANVAS.load_mesh(mesh)
            koko.FAB.set_input(asdf)

        elif path[-4:] == '.vol':
            self.mode = 'vol'
            koko.IMPORT.set_target(self.directory, self.filename)