Using Blender Python to create maille.
View previous topic | View next topic >
Post new topic Reply to topic
M.A.I.L. Forum Index -> Knitting Circle
   
Author Message

Joined: January 17, 2013
Posts: 373
Submissions: 5
Location: Probably in the garage...

Reply with quote
Posted on Thu Nov 14, 2013 2:16 pm || Last edited by Levi on Thu Nov 14, 2013 8:29 pm; edited 1 time in total
Link to Post: Link to Post

Levi wrote:
I have a bunch a garbage code to clean up and a bunch of commenting to insert but I'm almost done.


That being said the main part of the code is below. As is it generates each ring in a line, they are not weaved. Adding it into the loops is where I'm at currently. Test.png is a 6x2 pixel image with white, black, orange, blue, yellow and green pixels.

Also this is not a copy, paste and click Run Script type of script. The script needs to be saved to a text file locally. Then from the Blender console create a link to the file

file = "/home/levi/Desktop/Blends/Scripts/test.txt"

then compile and run it via

exec(compile(open(file).read(), file, 'exec'))

Code:

img_file = "/home/levi/Pictures/Test/test.png"
img = D.images.load(img_file)
pixels = list(img.pixels)
grouped_list = [pixels[ipx:ipx+4] for ipx in range(0, len(pixels), 4)]
obj_Torus = bpy.ops.mesh.primitive_torus_add

for i in range(-0,len(grouped_list)):
    tpix = (len(grouped_list))
    print('Total Pixels Found:', tpix)
    spixnum = i
    spix = (grouped_list[i])
    print('Selected pixed number:',spixnum)
    spixcola = grouped_list[i]
    print ('Selected Pixel Color with Alpha:',spixcola)
    spixRGB = spixcola[:3]
    spixcol = spixRGB
    print('Selected Pixel Color no Alpha:',spixcol)
    mat = D.materials.new('TestMat')
    mat.diffuse_color = spixcol
    obj_Torus(location=(i-0.5,i-0.5,1))
    C.object.data.materials.append(mat)



Gotta get to work, more when I get back.


Mostly Harmless

Joined: January 17, 2013
Posts: 373
Submissions: 5
Location: Probably in the garage...

Reply with quote
Posted on Thu Nov 14, 2013 5:17 pm
Link to Post: Link to Post

Changes that need to be made to the script.

1.Split the pixels into rows. So they are not in the grouped_list or split the grouped_list into rows. Lots of options here.

2. Loop through the rows creating the rings and placing them in the proper orientation for E4in1.

3. Add different loops for different weaves.

The stiff row and physics don't need to be applied, they can but it's not needed.

Edit:

The above script ran against my test image (also shown in image) creates the rings you see. Blender reads the pixel data from the bottom left of the image then wraps around to the start of the next row up, which is why the first and last rings are black and the two middle ones are white. I only spent a few minutes testing file compatibility but off the top .jpg does not read properly.



For now, keep the image size small. A 50x50 image will generate 2500 rings, based on the previous testing I expect it to take 15-20 minutes to do so. Larger images will take an obscenely long time to generate, a 200x200 image will generate 40000 rings. I hope you have a whole day or two for the script to run at that size.


Mostly Harmless

Joined: January 17, 2013
Posts: 373
Submissions: 5
Location: Probably in the garage...

Reply with quote
Posted on Sat Nov 23, 2013 6:18 pm
Link to Post: Link to Post

Having shifted gears a bit, I've nearly finished the tutorial (updated again to work with 2.69). I wish I could say it's ready to submit but I want to make sure it's as simple and accurate as possible so I'm having a few other eyes go over it before putting it in the queue.

Here is a little teaser, this is the end result of all the weaves covered in the tutorial.



I have Copper, Stainless Steel and Gold saw cut rings making a big 2in1 chain, a smaller 4in2 chain, a little sheet of J4in1, a sweet pea chain, a byzantine chain, a piece of JPL3, a sheet of J8in1 all sitting on a sheet of E4in1.

The tutorial covers everything required to make those chains so they look just like those shown above.

Almost there...then I can refocus on BlenderIGP and the scripting tutorial(s).


Mostly Harmless

Joined: January 17, 2013
Posts: 373
Submissions: 5
Location: Probably in the garage...

Reply with quote
Posted on Sun Nov 24, 2013 1:55 am
Link to Post: Link to Post

The tutorial has been submitted to the queue, awaiting approval.


Mostly Harmless

Joined: September 24, 2012
Posts: 93
Submissions: 1
Location: Stockholm, Sweden

Reply with quote
Posted on Mon Nov 25, 2013 1:45 pm
Link to Post: Link to Post

Lorraine better approve it Smile

Nice results Lavi. I'm struggling with programming that pay my rent Sad (Who came up with work?)

BR
C

Joined: January 17, 2013
Posts: 373
Submissions: 5
Location: Probably in the garage...

Reply with quote
Posted on Mon Nov 25, 2013 2:51 pm
Link to Post: Link to Post

Thanks Carolides.

The tut may take a little while to get approved, it's a bit long... kinda like 10 pages long in a text editor. Surprised


Mostly Harmless

Joined: September 24, 2012
Posts: 93
Submissions: 1
Location: Stockholm, Sweden

Reply with quote
Posted on Tue Nov 26, 2013 6:50 am
Link to Post: Link to Post

Levi wrote:
Thanks Carolides.

The tut may take a little while to get approved, it's a bit long... kinda like 10 pages long in a text editor. Surprised


Omg!
I guess it will be helpful for many.

Br
C

Joined: September 30, 2012
Posts: 255
Submissions: 13
Location: Sweden

Reply with quote
Posted on Tue Nov 26, 2013 8:11 am
Link to Post: Link to Post

Carolides wrote:

I guess it will be helpful for many


Depends on whether or not Blender would like to cooperate Uber Raz


"When in doubt... C4" - Jamie Hyneman

My YouTube channel: http://www.youtube.com/wpa09
My band: http://www.facebook.com/carnosus

"This is an art form, and we love to be recognized for our own work, and we'd all hope not to be confused with someone else."
- Charon, March 27, 2009

Joined: January 17, 2013
Posts: 373
Submissions: 5
Location: Probably in the garage...

Reply with quote
Posted on Tue Nov 26, 2013 2:10 pm
Link to Post: Link to Post

Carolides: I think it will help anyone that wants to learn how to use Blender but It's particularly helpful when it comes to simulating chainmaille.
It's a long one but it quite clearly covers every step from making a single ring, saw cutting it, applying materials, applying physics, creating a floor, adding lighting, rendering the scene, creating a chain, growing the chain along with instructions on how to build other chains and weaves from the items created previously.
After 20 years of doing IT support, I'm painfully aware that if instructions are not perfectly clear, they will be misconstrued. I've done pretty much all I can to make the steps as easy to follow but that of course adds to the length of the text. If it were code, there would be 5 lines of comments verbosely explaining each single line of actual code.

WpA09: No scripting involved, just clicks and keyboard shortcuts.


Mostly Harmless

Joined: January 17, 2013
Posts: 373
Submissions: 5
Location: Probably in the garage...

Reply with quote
Posted on Wed Dec 04, 2013 12:56 am
Link to Post: Link to Post

Ok, while we wait for the tutorial why not play around with BlenderIGP Script v1

The script takes a .png image and weaves it into a sheet of European 4 in 1.

Here's an example of what my 12x4 test image (shown enlarged in top left) creates when processed by the script.



You can download the script here and my tiny little test image here

Just about anyone should be able to understand the code given all of my commenting.

Code:

#
#BlenderIGP Script V.1 by Levi.
#This file can be downloaded as a text file.
#http://www.lostsensesdesigns.ca/blender/blenderIGP/BlenderIGPv1.txt
#
#This script reads pixel data from an image file then reconstucts the image in Blender using tori or rings.
#as it goes it weaves the rings into a virtual sheet of european 4 in 1 maille.
#Physics are applied to the individual rings allowing you to test the virtual sheet in a 'real-world' way.
#Primary use is meant for creating maille inlays and patterns but I'm sure it can be adapted for other uses.
#
#Usage, save the script to a local file. In Blender, change to scripting view then click Text > Open Text Block.
#Browse to the location of the saved script and confirm.
#In the console type without double-quotes "file='/pathtoscript/thescript.txt'"
#Change the path to match the location of your script,press Enter.
#Now that python knows what script to use, lets run it.
#In the console again without the double-quotes type "exec(compile(open).read(), file, 'exec'))" and press Enter.
#
#Keep the image size small!
#A 30x30 image will generate 900 rings in around 15-30 seconds.
#A 200x200 image will generate 40000 rings but it will probably take at least 8 hours to complete.
#While running the script Blender will be totally unresponsive to keyboard commands or mouse clicks.
#
#I'm not a coder, this is my first Python script I'm sure there are "better ways" to do some of this.
#Code improvements gladly accepted.
#
#Planned Version 2 improvements: Export ring list to .txt or csv with location and color. Dynamic AR adjustments. #Different weaves.
#
#
#This project is based on the idea behind Zlosk's IGP, and/or aganim's CanvasIGP. Paint an image onto rings.
#http://www.zlosk.com/pgmg/igp/
#http://andrewganim.com/canvas-igp
#Carolides on the Maille Artisans Forum gave me enough code and inspiration to create this Blender script.
#http://mailleartisans.org/board/viewtopic.php?t=18342
#
#Enjoy!
#
#
##
#Path to local image file.
#I'm using Linux, in Windows you would want to specify something like.
#"C:\Users\yourusername\pictures\test\test12x4.png" for example.
#
img_file = "/home/levi/Pictures/Test/test12x4.png"
##
#Load the specified image as "img".
#
img = D.images.load(img_file)
##
#Create a list of the image pixel data and save it as "pixels".
#
pixels = list(img.pixels)
##
#Read the image dimensions and saves them to "w", "width" and "h", "height".
w = width = img.size[0]
h = height = img.size[1]
##
#Inserts a single blank line, increases readability of the console.
#
print('\n')
##
#Script start banner.
#
print('<Starting>','\n')
##
#Lists the name of the image file being processed.
#
print('Image Name:',img_file,'\n')
##
#Lists the width and height for the image.
#
print('Width:',w,'Height:',h, '\n')
##
#Determines total pixels based on the the width and height.
#
t_img_pix=w*h
##
#Lists the total amount of pixels in the image.
#
print('Total Img Pix:',t_img_pix,'\n')
##
#Define the object to add, a torus or ring in this example.
#
obj_Torus = bpy.ops.mesh.primitive_torus_add
#obj_Sphere = bpy.ops.mesh.primitive_uv_sphere_add #Pixels are little dots, using spheres to represent them seems logical.
obj_Plane = bpy.ops.mesh.primitive_plane_add
##
#
#Creates an array of the pixel color values, excluding alpha then saves it to a list named "grouped_list".
#
grouped_list = [pixels[ipx:ipx+3] for ipx in range(0, len(pixels), 4)]
##
#
#Copies the list of grouped pixels to a new list named "p_group".
#
p_group=grouped_list
##
#
#Determines the length or item count in the "p_group" and saves it to "allpix".
#This should always be equal to "t_img_pix" but just in case it's not, let's check to be sure.
#The only likely cause for this step to fail is using the wrong image type.
#Currently only tested to work with .png files.
#
allpix = len(grouped_list)
print('Total Pixels Found:',allpix,'\n')
if allpix!=t_img_pix:
    print('The numbers do not add up! Something may be wrong with your image! \n')
else:
    print('The numbers add up, moving on. \n')
#
##
#A function to figure out the pixel rows and columns.
def idx_to_co(idx, width):
    r = int(idx / width)
    c = idx % width
    return r, c

##
#Prints all of the pixel data to the console
print('All of the pixel data found:',p_group,'\n')
#
rpad=0       #Used to increment the space between rows along the Y axis.
pix=0       #Should default to 0 but I'll set it anyway.
##
for pix in range(0,len(p_group)):        #For each pixel in the list do the following.
    ##
    #Pull the selected pixel data from "p_group".
    spix=(p_group[pix])
    ##
    #Prints the current pixel data to the console.
    print('Selected Pixel:',pix,'\n Color Data:',spix,'\n Row and Column:',idx_to_co(pix, w),'\n')
    ##
    #Next two lines create the new material and apply the selected pixel color data.
    #
    mat = D.materials.new('name')
    mat.diffuse_color = spix
    ##
    #These three lines get the row and column numbers for the selected pixel.
    #
    rc_count=idx_to_co(pix,w)    #Uses the pixel number to find the rows and columns.
    cnum=rc_count[1]       #Gets the colunm number which equates to the X axis.
    rnum=rc_count[0]       #Gets the row number which equates to the Y axix.
    #
    ##
    #Now set the location of the object based on the row and column count.
    #
    xloc=cnum          #Uses the row number for the X axis location.
    yloc=rnum+rpad       #Uses the column number along with the padding for the Y location.
    zloc=1          #Sets the Z axis to 1.
    new_loc=(xloc,yloc,zloc)    #Saves the location data to "new_loc"
    print('New Loc:',new_loc,'\n') #Prints the location of the ring to the console.
    if cnum==w-1:  #After the first row is complete add the rings minor radius to the padding value for all remaining rows.
        rpad+=0.17
    #
    ##
    #Creates a Torus at the new location based on "new_loc" as determined above.
    #
    obj_Torus(location=(new_loc), major_radius=1.0, minor_radius=0.17, major_segments=48, minor_segments=48)
    ##
    #Appends the previously made material to the sphere.
    #
    C.object.data.materials.append(mat)
    ##
    #Applies rotation to the rings. Need to flip every other row so the rings weave into E4in1.
    #
    if rnum%2==0: #if the row count is even, apply a .55 euler rotation to the ring.
        yr=(0.55)
    else:         #otherwise the count is odd, apply a -.55 euler rotation to the ring.
        yr=(-0.55)
    xr=0   #No rotation on the X axis required
    zr=0   #No rotation on the Y axis required
    rotate=[xr,yr,zr] #Saves the rotation values to "rotate"
    print('Rotation:',rotate[:],'\n') #Prints the rotation values to the console.
    bpy.context.active_object.rotation_euler=[xr,yr,zr] #Finally applies the rotation to the current ring.
    ##
    #Applies "Shade Smooth" to the current ring.
    #
    bpy.ops.object.shade_smooth()
    #
    ##
    #Applies a rigidbody, a mesh, sets the collision margin and makes the ring active.
    bpy.ops.rigidbody.object_add(type='ACTIVE')
    bpy.context.object.rigid_body.collision_shape = 'MESH'           
    bpy.context.object.rigid_body.collision_margin = 0.001
    bpy.context.object.rigid_body.enabled = True
##
#Set the rigid body world settings so the rings behave like rings.   
bpy.context.scene.rigidbody_world.steps_per_second=360
bpy.context.scene.rigidbody_world.solver_iterations=40
##
obj_Plane(location=(w/2,h/2,0))
bpy.context.object.scale=(w,h,0.5)
bpy.ops.rigidbody.object_add(type='ACTIVE')
bpy.context.object.rigid_body.collision_shape = 'BOX'
bpy.context.object.rigid_body.enabled = False
#Advises that the script has completed.
#
print('<All> \n')
#
#The end of script.


Code only no comments

Code:

img_file = "/home/levi/Pictures/Test/test12x4.png"
img = D.images.load(img_file)
pixels = list(img.pixels)
w = width = img.size[0]
h = height = img.size[1]
print('\n')
print('<Starting>','\n')
print('Image Name:',img_file,'\n')
print('Width:',w,'Height:',h, '\n')
t_img_pix=w*h
print('Total Img Pix:',t_img_pix,'\n')
obj_Torus = bpy.ops.mesh.primitive_torus_add
obj_Plane = bpy.ops.mesh.primitive_plane_add
grouped_list = [pixels[ipx:ipx+3] for ipx in range(0, len(pixels), 4)]
p_group=grouped_list
allpix = len(grouped_list)
print('Total Pixels Found:',allpix,'\n')
if allpix!=t_img_pix:
    print('The numbers do not add up! Something may be wrong with your image! \n')
else:
    print('The numbers add up, moving on. \n')

def idx_to_co(idx, width):
    r = int(idx / width)
    c = idx % width
    return r, c


print('All of the pixel data found:',p_group,'\n')
rpad=0    
pix=0
for pix in range(0,len(p_group)):       
    spix=(p_group[pix])
    print('Selected Pixel:',pix,'\n Color Data:',spix,'\n Row and Column:',idx_to_co(pix, w),'\n')
    mat = D.materials.new('name')
    mat.diffuse_color = spix
    rc_count=idx_to_co(pix,w)    
    cnum=rc_count[1]       
    rnum=rc_count[0]       
    xloc=cnum          
    yloc=rnum+rpad       
    zloc=1          
    new_loc=(xloc,yloc,zloc)    
    print('New Loc:',new_loc,'\n')
    if cnum==w-1:
        rpad+=0.17
    obj_Torus(location=(new_loc), major_radius=1.0, minor_radius=0.17, major_segments=48, minor_segments=48)
    C.object.data.materials.append(mat)
    if rnum%2==0:
        yr=(0.55)
    else:
        yr=(-0.55)
    xr=0
    zr=0
    rotate=[xr,yr,zr]
    print('Rotation:',rotate[:],'\n')
    bpy.context.active_object.rotation_euler=[xr,yr,zr]
    bpy.ops.object.shade_smooth()
    bpy.ops.rigidbody.object_add(type='ACTIVE')
    bpy.context.object.rigid_body.collision_shape = 'MESH'           
    bpy.context.object.rigid_body.collision_margin = 0.001
    bpy.context.object.rigid_body.enabled = True
bpy.context.scene.rigidbody_world.steps_per_second=360
bpy.context.scene.rigidbody_world.solver_iterations=40
obj_Plane(location=(w/2,h/2,0))
bpy.context.object.scale=(w,h,0.5)
bpy.ops.rigidbody.object_add(type='ACTIVE')
bpy.context.object.rigid_body.collision_shape = 'BOX'
bpy.context.object.rigid_body.enabled = False
print('<All> \n')


Have fun!


Mostly Harmless

Joined: January 17, 2013
Posts: 373
Submissions: 5
Location: Probably in the garage...

Reply with quote
Posted on Sun Dec 08, 2013 12:58 am
Link to Post: Link to Post

That test image is kinda bland so here's one of someone a bit more familiar.

MegaMan at 60x60 (source image inset top right). The lighting in the scene is quite poor so the colors look darker than they should but I think you get the idea.




Mostly Harmless

Joined: September 24, 2012
Posts: 93
Submissions: 1
Location: Stockholm, Sweden

Reply with quote
Posted on Mon Dec 09, 2013 2:17 pm
Link to Post: Link to Post

HAHA

You're a Blender monster Smile

BR
C

Joined: January 17, 2013
Posts: 373
Submissions: 5
Location: Probably in the garage...

Reply with quote
Posted on Wed Dec 11, 2013 1:44 am
Link to Post: Link to Post

Carolides: In looking over your original post from almost three months ago, I think the script now meets all of your basic requests. The only major limitations are time required for the script to run and the insane amounts of RAM that Blender requires while doing so. 8GB is just enough to process a 200x200 image you probably won't be able to play the animation or render it but the rings will be created. With a few hundred GB of RAM and a couple days you could probably do a 1920x1080 image.

Changed a few things in the script. New version here.

v1 to v1.1 changes.
-Corrected the weave description, it weaves european 6 in 1 not 4 in 1. Doh!
-Disabled the plane and replaced it with an Inanimate Carbon Rod that the sheet hangs from. Play the animation to see your sheet drop and hang like a real sheet of maille.

Code:

obj_ICR = bpy.ops.mesh.primitive_cylinder_add

#In Rod We Trust! Saving the day again is our good friend "Inanimate Carbon Rod"
#Creates a long cylinder or rod placed inside of the top row that stops the rings from falling.
obj_ICR(location=(w/2-1,h+rpad-0.67,1),radius=0.17,depth=w+3) #Needs to be tweaked to use AR values.
mat = D.materials.new('name')
mat.diffuse_color = 0.027, 0.8, 0
bpy.context.active_object.rotation_euler=(0,1.5708,0)
bpy.ops.rigidbody.object_add(type='ACTIVE')
bpy.context.object.rigid_body.collision_shape='MESH'
bpy.context.object.rigid_body.collision_margin = 0.001
bpy.context.object.rigid_body.enabled=False
C.object.data.materials.append(mat)
bpy.ops.object.shade_smooth()


Here is a pic of the ICR in action.

Ohhh, Ahhhh...

In case you don't know, inanimate carbon rod is from S5E15 of the Simpsons. One of my favourite episodes, "Deep Space Homer" where Homer goes to space and uses an inanimate carbon rod to close the door on the space shuttle saving the day. The rod received a parade and was featured on the cover of Time, Homer received no recognition.

FYI: If you want to get the sheet hanging vertically a bit quicker. After running the script, from the bottom menu click "Select" > "Select All by Type" > "Mesh" then with your cursor in the viewport press R, X, 90, Enter. Now that the sheet is already vertical when you play the animation you'll avoid the swinging, the sheet will just stretch downwards as gravity pulls on it.


Mostly Harmless

Joined: March 26, 2002
Posts: 1945
Submissions: 582
Location: Chainmailland, Chainmailia

Re: Blender Tutorial
Reply with quote
Posted on Fri Jan 03, 2014 9:40 am
Link to Post: Link to Post

It's coming folks. The tutorial, that is. I know it's been taking awhile, but it's nearing completion. Levi and I have been working hard to make it as comprehensive as possible. And it will be worth the wait.


Chainmailbasket.com (2019-01-01) - 376 + 79

Joined: December 22, 2007
Posts: 4610
Submissions: 106
Location: Hampton, Virginia USA

Re: Blender Tutorial
Reply with quote
Posted on Fri Jan 03, 2014 9:18 pm
Link to Post: Link to Post

Chainmailbasket_com wrote:
It's coming folks. The tutorial, that is. I know it's been taking awhile, but it's nearing completion. Levi and I have been working hard to make it as comprehensive as possible. And it will be worth the wait.

YAY!


"I am a leaf on the wind." ~ Wash
Lorraine's Chains
Gallery Submission Guidelines

Post new topic Reply to topic
Jump to:  
Page 6 of 7. Goto page Previous  1, 2, 3, 4, 5, 6, 7  Next
All times are GMT. The time now is Sat Apr 04, 2020 10:30 am
M.A.I.L. Forum Index -> Knitting Circle
Display posts from previous: