Segmentation Fault with an addon...

Well, here is my addon:


# -*- coding: utf-8 -*-

import bpy
import os
import sys
import subprocess
import time
from threading import * 


class Repeat(Thread):
    def __init__(self,delay,function,*args,**kwargs):
        Thread.__init__(self)
        self.abort = Event()
        self.delay = delay
        self.args = args
        self.kwargs = kwargs
        self.function = function
    def stop(self):
        self.abort.set()
    def run(self):
        while not self.abort.isSet():
            self.function(*self.args,**self.kwargs)
            self.abort.wait(self.delay)

class ExportToGIMP(bpy.types.Operator):
    bl_idname = "uv.exporttogimp"
    bl_label = "Export to GIMP"
    
    def execute(self, context):
        try:
            os.remove("/home/antoni4040/blender.txt")
        except: 
            pass            
        self.filepath = os.path.join(os.path.dirname(bpy.data.filepath), "Layout")
        bpy.ops.uv.export_layout(filepath=self.filepath, check_existing=True, export_all=False, modified=False, mode='PNG', size=(1024, 1024), opacity=0.25, tessellated=False)
        self.files = os.path.dirname(bpy.data.filepath)
        cmd = " (python-fu-bgsync RUN-NONINTERACTIVE)"
        subprocess.Popen(['gimp', '-b', cmd])
        self.object = bpy.context.active_object
        self.file = Repeat(3, self.up)
        self.file.start()
        return {'FINISHED'};
    
    def up(self):
        try:
            f = open("/home/antoni4040/blender.txt", "r")
            string = f.read()
            if "ok" in string:
                self.materialize()
                self.doit()
                self.file.stop()
            else:
                pass     
        except:
            pass    

    def doit(self):
        r = Repeat(3, self.update)
        r.start()    

    def materialize(self):
        self.layout_texture = bpy.data.textures.new(name = "Layout_Texture", type = "IMAGE")
        self.material = bpy.data.materials.new(name="Layout")
        self.material_texture = self.material.texture_slots.add()
        self.material_texture.texture = self.layout_texture
        self.material_texture.texture_coords = "UV"
        self.filepath2 = "/home/antoni4040/Έγγραφα/Layout1.png"
        self.texture_image = bpy.data.images.load(self.filepath2)                       
        self.layout_texture.image = self.texture_image
        self.con_obj = self.object.data
        self.con_obj.materials.append(self.material)
        bpy.data.screens['UV Editing'].areas[1].spaces[0].image = self.texture_image

    def update(self):
        self.layout_texture.image.reload()
        for area in bpy.data.screens['Default'].areas:
            if area.type in ['IMAGE_EDITOR', 'VIEW_3D']:
                area.tag_redraw()
            
def exporttogimp_menu(self, context):
    self.layout.operator(ExportToGIMP.bl_idname, text="Export To GIMP")

bpy.utils.register_class(ExportToGIMP)
bpy.types.IMAGE_MT_uvs.append(exporttogimp_menu)

The only problems are the Segmentation Faults… What’s going wrong?

Hey antoni

Firstly please put your code in [noparse]


[/noparse] tags.

Try getting rid of the threading.

I can’t get rid of it… There’s no other way to do the job…

Blender isn’t really designed to have python scripts running in threads…

http://www.blender.org/documentation/blender_python_api_2_64_6/info_gotcha.html#strange-errors-using-threading-module

Do you know any way to use an infinite while loop without freezing Blender?

My approach to this would be to use a modal timer operator. There is a sample under the templates menu in the text editor. I like using boolProperties, with an update method, as switches to turn the op on / off. Use the timer tick as the loop.

What I’ve found with using threading is it’s Ok-ish if you are not changing any blender properties from within the thread. For instance set up a global var in a script and do what you want with it with threading… but as soon as you start touching blender props… CRASH.

Another suggestion is, when posting code, have a StringProperty(subtype=‘FILE_PATH’) for files, subtype=‘DIR_PATH’ for folders, set up so the user can input their own file path / paths . When I see something like “/home/antoni4040/Έγγραφα/Layout1.png” my lazy bones gene takes over and I couldn’t be bothered setting up the files required to test.

Hope this helps.

I don’t really understand it, to tell you the truth…

Ok, you mean something like this:


import bpy
import exporttogimp
ready = "ok"

class Timer(bpy.types.Operator):
    bl_label = "Timer"
    bl_idname = "wm.modal_timer_operator"
    _timer = None
     
    def modal(self, context, event):
        if event.type == 'ESC':
            return self.cancel(context)
 
        if event.type == 'TIMER':
            try:
                f = open("/home/antoni4040/blender.txt", "r")
                string = f.read()
                if "ok" in string:
                    clas = ExportToGIMP
                    if ready == "ok":
                        clas.materialize()
                        ready = "notok"
                    else:
                          clas.update() 
                else:
                    pass     
            except:
                pass    
      
        return {'PASS_THROUGH'}

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

    def cancel(self, context):
        context.window_manager.event_timer_remove(self._timer)
        return {'CANCELLED'}

def registerit():
    bpy.utils.register_class(Timer)


Well, it doesn’t do anything… Is there something wrong?

Hi,

You need to register an operator to make it work. If you make your code into an addon then blender calls the un / register methods for you when you dis / enable and addon, if run from the script editor you will see


if __name__ == "__main__":
    register()

on the bottom of blender scripts This calls the register for you and makes the operator visible to blender. You call it using


bpy.ops.wm.modal_timer_operator()
bpy.ops.uv.export_to_gimp()

importing it, you are referring to the class rather than the class instance, the operator in this case.

My suggestion would be to recode your exporttogimp class to not be an operator but some kind of blender2gimp api class, instance and call it from the modal timer operator, using its time loop to check for the “ok” condition. This should take away your need for the threading.

Use this api class again in importtogimp and any other image manipulation functionality.

Also are you writing to a file in gimp and reading it in blender to look for "ok?,

in a script I’m writing to call GDAL executables I pipe std out to a variable with communicate(), and then search for fields.
This snippet calls the gdal_info executable.


    def info(self):
        cmd = '"%s" -mm -stats "%s"' % (self.map_exe("gdalinfo"), self.map_input_path)
        print(cmd)
        output = Popen(shlex.split(cmd), stdout=PIPE).communicate()[0].decode("utf-8")
        #print("gdal_translate %s" % argstr)


        h_max = h_min = 0
        for line in output.split("
"):
            if line.find("STATISTICS_MINIMUM") > -1:
                h_min = int(line.split("=")[1])
            elif line.find("STATISTICS_MAXIMUM") > -1:
                h_max = int(line.split("=")[1])
            print(line)
        print(h_min, h_max)

Well, I have this two .py files:


# -*- coding: utf-8 -*-

import bpy
import os
import sys
import subprocess
import time
from threading import * 
sys.path.append("/home/antoni4040/Λήψεις/blender-2.64-linux-glibc27-i686/") 
import timer

class ExportToGIMP(bpy.types.Operator):
    bl_idname = "uv.exporttogimp"
    bl_label = "Export to GIMP"
    
    def execute(self, context):
        try:
            os.remove("/home/antoni4040/blender.txt")
        except: 
            pass            
        self.filepath = os.path.join(os.path.dirname(bpy.data.filepath), "Layout")
        bpy.ops.uv.export_layout(filepath=self.filepath, check_existing=True, export_all=False, modified=False, mode='PNG', size=(1024, 1024), opacity=0.25, tessellated=False)
        cmd = " (python-fu-bgsync RUN-NONINTERACTIVE)"
        subprocess.Popen(['gimp', '-b', cmd])
        timer.registerit()
        bpy.ops.wm.modal_timer_operator()
        return {'FINISHED'};

    def materialize(self):
        self.layout_texture = bpy.data.textures.new(name = "Layout_Texture", type = "IMAGE")
        self.material = bpy.data.materials.new(name="Layout")
        self.material_texture = self.material.texture_slots.add()
        self.material_texture.texture = self.layout_texture
        self.material_texture.texture_coords = "UV"
        self.object = bpy.context.active_object
        self.filepath2 = "/home/antoni4040/Έγγραφα/Layout1.png"
        self.texture_image = bpy.data.images.load(self.filepath2)                       
        self.layout_texture.image = self.texture_image
        self.con_obj = self.object.data
        self.con_obj.materials.append(self.material)
        bpy.data.screens['UV Editing'].areas[1].spaces[0].image = self.texture_image
          
    def update(self):
        self.layout_texture.image.reload()
                    
def exporttogimp_menu(self, context):
    self.layout.operator(ExportToGIMP.bl_idname, text="Export To GIMP")

bpy.utils.register_class(ExportToGIMP)
bpy.types.IMAGE_MT_uvs.append(exporttogimp_menu) 




import bpy
import exporttogimp

class Timer(bpy.types.Operator):
    bl_label = "Timer"
    bl_idname = "wm.modal_timer_operator"
    _timer = None
    ready = "ok" 
     
    def modal(self, context, event):
        if event.type == 'ESC':
            return self.cancel(context)
 
        if event.type == 'TIMER':
            try:
                f = open("/home/antoni4040/blender.txt", "r")
                string = f.read()
                if "ok" in string:
                    clas = ExportToGIMP
                    if ready == "ok":
                        clas.materialize()
                        ready = "notok"
                    else:
                          clas.update() 
                else:
                    pass     
            except:
                pass    
      
        return {'PASS_THROUGH'}

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

    def cancel(self, context):
        context.window_manager.event_timer_remove(self._timer)
        return {'CANCELLED'}

def registerit():
    bpy.utils.register_class(Timer)


I thought that this would be enough, but, firstly, it registers ExportToGimp twice, and, secondly it does nothing…
If you find them silly, well, have in mind that I’m not exactly the best programmer ever…
Just trying to gain some experience…

Well, what about this?


# -*- coding: utf-8 -*-

import bpy
import sys
import os
import subprocess

class Timer(bpy.types.Operator):
    bl_label = "Timer"
    bl_idname = "wm.modal_timer_operator"
    _timer = None
                
    def modal(self, context, event):
        if event.type == 'ESC':
            return self.cancel(context)
 
        if event.type == 'TIMER':
            try:
                if self.ready2 == "notok":
                    pass
            except:
                self.executemain()
                self.ready2 = "notok"                             
            try:
                f = open("/home/antoni4040/blender.txt", "r")
                string = f.read()
                if "ok" in string:
                    try:
                        if self.ready == "notok":
                            self.layout_texture.image.reload()                        
                    except:
                        self.materialize()
                        self.ready = "notok"    
                else:
                    pass     
            except:
                pass    
      
        return {'PASS_THROUGH'}

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

    def cancel(self, context):
        context.window_manager.event_timer_remove(self._timer)
        return {'CANCELLED'}
    
    def executemain(self):
        try:
            os.remove("/home/antoni4040/blender.txt")
        except: 
            pass            
        self.filepath = os.path.join(os.path.dirname(bpy.data.filepath), "Layout")
        bpy.ops.uv.export_layout(filepath=self.filepath, check_existing=True, export_all=False, modified=False, mode='PNG', size=(1024, 1024), opacity=0.25, tessellated=False)
        cmd = " (python-fu-bgsync RUN-NONINTERACTIVE)"
        subprocess.Popen(['gimp', '-b', cmd])
        
    def materialize(self):
        self.layout_texture = bpy.data.textures.new(name = "Layout_Texture", type = "IMAGE")
        self.material = bpy.data.materials.new(name="Layout")
        self.material_texture = self.material.texture_slots.add()
        self.material_texture.texture = self.layout_texture
        self.material_texture.texture_coords = "UV"
        self.object = bpy.context.active_object
        self.filepath2 = "/home/antoni4040/Έγγραφα/Layout1.png"
        self.texture_image = bpy.data.images.load(self.filepath2)                       
        self.layout_texture.image = self.texture_image
        self.con_obj = self.object.data
        self.con_obj.materials.append(self.material)
        bpy.data.screens['UV Editing'].areas[1].spaces[0].image = self.texture_image        
        
def exporttogimp_menu(self, context):
    self.layout.operator(Timer.bl_idname, text="Export To GIMP")

if __name__ == "__main__":
    bpy.utils.register_class(Timer)
    bpy.types.IMAGE_MT_uvs.append(exporttogimp_menu)     
    


It works as it should be, with one exception, I get this error when it tryes to reload the image:
Reached EOF while decoding PNG
IMB_ibImageFromMemory: unknown fileformat (/home/antoni4040/Έγγραφα/Layout1.png)

???

Fixed a bit:


# -*- coding: utf-8 -*-

import bpy
import sys
import os
import subprocess

class Timer(bpy.types.Operator):
    bl_label = "Timer"
    bl_idname = "wm.modal_timer_operator"
    _timer = None
                
    def modal(self, context, event):
        if event.type == 'ESC':
            return self.cancel(context)
 
        if event.type == 'TIMER':
            try:
                if self.ready2 == "notok":
                    pass
            except:
                self.executemain()
                self.ready2 = "notok"                             
            try:
                f = open("/home/antoni4040/blender.txt", "r")
                string = f.read()
                if "ok" in string:
                    try:
                        if self.ready == "notok":
                            try:
                                if os.path.isfile("/home/antoni4040/Έγγραφα/Layout1.png"):
                                    self.layout_texture.image.reload()                        
                            except:
                                pass    
                    except:
                        self.materialize()
                        self.ready = "notok"    
                else:
                    pass     
            except:
                pass    
      
        return {'PASS_THROUGH'}

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

    def cancel(self, context):
        context.window_manager.event_timer_remove(self._timer)
        return {'CANCELLED'}
    
    def executemain(self):
        try:
            os.remove("/home/antoni4040/blender.txt")
        except: 
            pass            
        self.filepath = os.path.join(os.path.dirname(bpy.data.filepath), "Layout")
        bpy.ops.uv.export_layout(filepath=self.filepath, check_existing=True, export_all=False, modified=False, mode='PNG', size=(1024, 1024), opacity=0.25, tessellated=False)
        cmd = " (python-fu-bgsync RUN-NONINTERACTIVE)"
        subprocess.Popen(['gimp', '-b', cmd])
        
    def materialize(self):
        self.layout_texture = bpy.data.textures.new(name = "Layout_Texture", type = "IMAGE")
        self.material = bpy.data.materials.new(name="Layout")
        self.material_texture = self.material.texture_slots.add()
        self.material_texture.texture = self.layout_texture
        self.material_texture.texture_coords = "UV"
        self.object = bpy.context.active_object
        self.filepath2 = "/home/antoni4040/Έγγραφα/Layout1.png"
        self.texture_image = bpy.data.images.load(self.filepath2)                       
        self.layout_texture.image = self.texture_image
        self.con_obj = self.object.data
        self.con_obj.materials.append(self.material)
        bpy.data.screens['UV Editing'].areas[1].spaces[0].image = self.texture_image        
        
def exporttogimp_menu(self, context):
    self.layout.operator(Timer.bl_idname, text="Export To GIMP")

if __name__ == "__main__":
    bpy.utils.register_class(Timer)
    bpy.types.IMAGE_MT_uvs.append(exporttogimp_menu)     
    


This is really strange… I get this error again but only if I zoom-in/out from the UV/Image Editor? WTF?

Hi,

Updated to the latest version of gimp and now have python-fu support too. Clicking a button and using gimp is very very handy.

A couple quick questions.

bpy.ops.uv.export_layout() where do i find that?

and your gimp script.

Haven’t looked at your latest code

Did play around with getting the gimp batch to work on my windoze 32 system. set up class overriding the blender Image class…

A “gimp” ID property on the images would be an easy way to setup a list or dictionary to access them while blender is running.


 gimp_images = {name:GimpImage(image) for name, image in D.images.items() if "gimp" in image.keys()}

For the example I’ve simply added image 0 to the gimp_images list.

I’m on windoze and have a different gimp path. Added a gimp_path property on the scene. If it was an addon I would save this to a file once set.

Returned to threading for this one. … On win I have to manually press a key to close the gimp output window, when I do the image is changed and reloaded. If the gimp output window closed it would be so much better. Didn’t get around to trying your stoppable thread.

Used the output as a flag that gimp had finished its processing, and reloaded there to test. Using a modal timer op instead is discussed below.

I’ve used your flip script to test, and hacked this together from it to call the normalmap gimp plugin to take a displacement map a normal map.


from gimpfu import pdb, main, register, PF_STRING


def normal_map(file):
    image = pdb.gimp_file_load(file, file)
    drawable = pdb.gimp_image_get_active_layer(image)
    filter = 2
    minz = 0.0
    scale = 1.0
    wrap = 0
    height_source = 0
    alpha = 0
    conversion = 0
    dudv = 0 
    xinvert = 0
    yinvert = 0
    swapRGB = 0
    contrast = 0.0
    alphamap = drawable # not sure what to choose here.
    pdb.plug_in_normalmap(image, drawable, filter, minz, scale, wrap, height_source, alpha, conversion, dudv, xinvert, yinvert, swapRGB, contrast, alphamap)
    pdb.gimp_file_save(image, drawable, file, file)
    print "Normal Map"
    pdb.gimp_image_delete(image)


args = [(PF_STRING, 'file', 'GlobPattern', '*.*')]
register('python_disp2norm', '', '', '', '', '', '', '', args, [], normal_map)


main()




import bpy
from bpy.types import Image


from subprocess import Popen, PIPE, check_output


from multiprocessing import Process
import os
from threading import Thread, Event


gimp_images = {}




class GimpImage(Image):
    def __init__(self, obj):
        scene = bpy.context.scene
        self.gimp_path = scene.gimp_path
        pass
    
    def flip(self):
        
        print("Flip %s with gimp" % self.filepath_raw)


        cmd = '(python-flip RUN-NONINTERACTIVE "%s")' % self.filepath_raw.replace("\\", "/")
        output = check_output([self.gimp_path, '-i', '-b', cmd, '-b', '(gimp-quit 0)'])
        if output:
            print(output)
            self.reload()  # reload the image
            
    def disp2norm(self):


        
        print("Disp2Norm %s with gimp"% self.filepath_raw.replace("\\", "/"))
        
        #needed to replace sloshes with slashes
        cmd = '(python-disp2norm RUN-NONINTERACTIVE "%s")' % self.filepath_raw.replace("\\", "/")
        output = check_output([self.gimp_path, '-i', '-b', cmd, '-b', '(gimp-quit 0)'])
        if output:
            print(output)
            self.reload()  # reload the image




# some tests


# using scene.gimp_path to point to gimp executable


gimp_path = bpy.props.StringProperty(default = '', subtype='FILE_PATH')
bpy.types.Scene.gimp_path = gimp_path


context = bpy.context
scene = context.scene


img = bpy.data.images[0]
# append an instance to gimp_images


gimpimage = gimp_images[img.name] = GimpImage(img)


if os.path.isfile(scene.gimp_path):
    t = Thread(target=gimpimage.disp2norm)
    #t = Process(target=gimpimage.disp2norm)
    t.start()


# apply the image as a texture


tex = bpy.data.textures.new('tex', type='IMAGE')
tex.image = gimpimage

To sum up, my suggestion is to set up a class like above, and then set up your operators to operate on it rather than the all in one solution. I can’t test your code because of the scripts missing (mentioned above) and the hardcoded filenames. The text file you continually open, read, check for “Ok”… where is it set? A lot of my questions will be answered by the python-fu-bgsync script.

A modal timer op can be set up to call a method that opens a gimp subprocess when invoked (or in execute method before the modal_hander_add) , run modal until some property changes, then finalize (reload image apply to textures etc)

After a quick look at your recent code my first thought is you are doing too much and relying on too many try / except statements in your modal loop.

Well, here’s the Gimp plug-in:


#!/usr/bin/env python
# -*- coding: utf-8 -*-

from gimpfu import *
import time
import sys  
      
def blender_gimp_sync():
    im_dir = "/home/antoni4040/Έγγραφα/Layout.png"
    image = gimp.pdb.gimp_file_load(im_dir, im_dir)
    gimp.Display(image)
    layer = gimp.pdb.gimp_layer_new(image, 1024, 1024, RGBA_IMAGE, "PaintHere", 100.0, NORMAL_MODE)    
    image.add_layer(layer)
    drawable = pdb.gimp_image_get_active_layer(image)
    num = 1
    while True:
        time.sleep(3.0)
        if image is not None:  
            ims_dir = "/home/antoni4040/Έγγραφα/Layout1.png"
            gimp.pdb.file_png_save(image, drawable, ims_dir, ims_dir, 0, 0, 0, 0, 0, 0, 0)
            f = open("/home/antoni4040/blender.txt", "w")
            f.write("ok")
        else:
            pass
        
register(
    "python_fu_bgsync",
    "Blender-Gimp Sync",
    "Syncronize Gimp with Blender for texturing",
    "Antonis Karvelas",
    "Antonis Karvelas",
    "2012",
    "Sync",
    "",
    [],
    [],
    blender_gimp_sync,
    menu="<Image>/Image/Blender-Gimp"
)    
main()


You should find the image from the bpy.ops.uv.export_layout() in the same folder as the .blend file…
Thank you for your time, but I think that you fail to answer my final question(because, I think I’m pretty much done…):
Why the image gets lost if I zoom in/out from the UV/Image Editor?
I would appreciate it if you try my latest code…
P.S.: It might take some time to do so, but the image always gets lost…

  1. I want it to reload the image every timer tick…
  2. What print statements?

One endless loop writing it every 3 seconds, and another reading it every one second?

  1. What print statements?

really? I think this has to go into my too hard basket bud.

how about buying this addon for $5?

I don’t really need it, I just want to create it to help other people that wouldn’t like to pay for something so simple…

Any ideas? I will start hitting my head on the wall if I see this error another time…

I think I’m going to quit, I have other things to do…
One note for the devs, though:
FIX THE DAMN THREADS!!!
Personally, I would love that…