Multithreading in a macro?

Need help, or want to share a macro? Post here!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
Post Reply
kefir
Posts: 30
Joined: Wed Jul 23, 2014 9:03 pm

Multithreading in a macro?

Post by kefir »

Hi,

I'm slicing an object into multiple cross sections for laser cutting, using the script given here:

http://freecadbuch.de/doku.php?id=blog: ... -schneider

Here' my copy:

Code: Select all

from FreeCAD import Base
import Draft, Part
App=FreeCAD

def slicePart(ob):
	shape=ob.Shape
	g2=App.ActiveDocument.addObject("App::DocumentObjectGroup",str(ob.Label) + "_Slices")
	b=shape.BoundBox
	# print shape
	lx=b.XMax-b.XMin+5
	ly=b.YMax-b.YMin+5
	dh=4 # mm thickness per step
	h=b.ZMin + (dh / 2) # start halfway up the first step
	l=0
	bb=0
	while h < b.ZMax:
		# print h
		try:
			wires=list()
			for i in shape.slice(Base.Vector(0,0,1),h):
				wires.append(i)
			comp=Part.Compound(wires)
			slice=FreeCAD.ActiveDocument.addObject("Part::Feature","MySlice")
			slice.Shape=comp
			slice.purgeTouched()
#			del comp,wires
			t=FreeCAD.ActiveDocument.addObject("Part::Extrusion","Extrude")
			t.Base = slice
			t.Dir = (0,0,dh)
			t.Solid = True
			t.Placement.Base=FreeCAD.Vector(l,bb,-h)
			l += lx
			if l >400:
				l=0
				bb +=ly
			g2.addObject(t)
		except:
			print "FEHLER"
		h += dh
	# del shape
	App.activeDocument().recompute()
# main 

slicePart(App.ActiveDocument.Solid)
This is taking a long long time, so while waiting it occurred to me that this is only using one cpu core. Spliting this single object up into many cross sections seems like a fairly ideal task to multithread. Would this be possible to rewrite to use multithreading? If so, are there any pointers or examples?

Regards, Ketli
triplus
Veteran
Posts: 9471
Joined: Mon Dec 12, 2011 4:45 pm

Re: Multithreading in a macro?

Post by triplus »

You can start multiple instances of FreeCAD (depending on threads available) and in each instance you can run the macro that does only part of the job.

Edited while Ian already posted:

I just remembered another option. If you use OCC 6.9.0/OCE 0.18 and up and FreeCAD 0.17 development release. You could produce some faces and use them in the boolean operation with the base shape. Boolean section (boolean common could also be used) sounds appropriate. You can provide the faces to the boolean operation in the form of multiple arguments. And that should result in multithreading.

P.S. No multithreading but i guess fast results. You could try out Part -> Cross-sections... on the base shape and increase the number of Sections Count. Use Draft Downgrade on the result.
Last edited by triplus on Sat Feb 18, 2017 2:11 am, edited 4 times in total.
ian.rees
Posts: 696
Joined: Sun Jun 15, 2014 3:28 am
Contact:

Re: Multithreading in a macro?

Post by ian.rees »

To expand on what triplus said just a little - there's a headless FreeCADCmd binary that's ideal for this sort of thing. So the architecture would likely involve two scripts - the first script manages the FreeCAD tasks, and the second is used by each worker task to do part of the job. -Ian-
kefir
Posts: 30
Joined: Wed Jul 23, 2014 9:03 pm

Re: Multithreading in a macro?

Post by kefir »

Thanks for the input.

I had a chat with eivindkvedalen, and got the suggestion to try splitting my complex part into smaller parts with boolean operations first, and doing the cross sections later. I've tried, and the time reduction was dramatic, the script completes in less than 3 minutes, while running a basic Part -> Cross-sections took 2-3 hours! Goes to show that there's room for some optimisation in FreeCAD. :) With this speed increase, I'm not going to pursue the multithreading for now.

Here's my current code, which I simply run by pasting it into the python console in FreeCAD, and then call it as in the example given below.

Code: Select all

from FreeCAD import Base
import Draft
import Part
App=FreeCAD

def bisectCrossSections(obj, layerThickness, layerLocation=0.5): 
    '''bisectSliceWire(obj, layerThickness, layerLocation) splits an object
       into wires at layerThickness intervals along the Z axis, and distributes
       the wire shapes on a plane. If you want to laser cut a 3-dimensional
       object in 4mm plywood, run bisectSliceWire(object, 4), and you will get
       the laser cutting paths necessary.

       layerLocation is a range from [0.0, 1.0] that specifies where in each
       layerThickness segment the wire should cut, where 0.0 is the bottom,
       0.5 is the center, and 1.0 is the top. Values outside the accepted range
       are truncated.

       This script was initially based on slicePart() from the script available on
       http://freecadbuch.de/doku.php?id=blog:schnittmodell_eines_hauses_fuer_den_laser-schneider

       This script uses an optimisation for complex objects that includes
       cutting the original object into two, and then cutting each segment in
       two, repeating until the segments are suitably small. This has resulted
       in a time optimisation on the order of 50-60x on my laptop, for a freecad
       object that was created from a mesh, as shown in
       https://www.youtube.com/watch?v=avVNfIswkMU
    '''
    layerLocation=max(0.0, layerLocation) # enforce sensible limits
    layerLocation=min(1.0, layerLocation)
    sections = []   
    workSections = [obj]
    i = 0
    while len(workSections) > 0:
        o = workSections.pop(0)
        b = o.Shape.BoundBox
        lx = b.XLength
        ly = b.YLength
        lz = b.ZLength

        if lz < layerThickness:
            sections.append(o)
            continue

        label_boxbothalf = "box_bottom_half_{0}".format(i)
        label_bothalf = "bottom_half_{0}".format(i)
        label_tophalf = "top_half_{0}".format(i)

        # create bottom half with Intersection boolean op
        s=App.ActiveDocument.addObject("Part::Box",label_boxbothalf)
        s.Placement = App.Placement(App.Vector(b.XMin - 1,b.YMin - 1,b.ZMin - 1),App.Rotation(App.Vector(0,0,1),0))
        s.Length = lx + 2
        s.Width = ly + 2
        s.Height = (lz/2) + 1
        bottom_half = App.activeDocument().addObject("Part::MultiCommon",label_bothalf)
        bottom_half.Shapes = [o, s]

        # create top half with Difference boolean op
        top_half = App.activeDocument().addObject("Part::Cut",label_tophalf)
        top_half.Base = o
        top_half.Tool = s

        App.ActiveDocument.recompute()

        if bottom_half.Shape.BoundBox.ZLength > layerThickness*2:
            workSections.append(bottom_half)
        else:
            sections.append(bottom_half)

        if top_half.Shape.BoundBox.ZLength > layerThickness*2:
            workSections.append(top_half)
        else:
            sections.append(top_half)

        i = i + 1
 
    g2=App.ActiveDocument.addObject("App::DocumentObjectGroup",str(obj.Label) + "_Slices")

    total_zmin = obj.Shape.BoundBox.ZMin
    total_zmax = obj.Shape.BoundBox.ZMax
    lx = obj.Shape.BoundBox.XLength + 5
    ly = obj.Shape.BoundBox.YLength + 5

    out_x=0
    out_y=0
    start_z = total_zmin + (layerThickness * layerLocation)
    step = layerThickness
    total_step = step
    current_z = start_z + total_step
    while current_z <= total_zmax:
        # Find the correct section in sections[]
        for section in sections:
            if section.Shape.BoundBox.ZMin <= current_z and section.Shape.BoundBox.ZMax >= current_z:
                try:
                    wires = list()
                    for i in section.Shape.slice(FreeCAD.Base.Vector(0,0,1),current_z):
                        wires.append(i)
                    comp = Part.Compound(wires)
                    layer = FreeCAD.ActiveDocument.addObject("Part::Feature", "MyLayer")
                    layer.Shape = comp
                    layer.purgeTouched()
                    layer.Placement.Base = FreeCAD.Vector(out_x, out_y, -current_z)
                    out_x = out_x + lx

                    if out_x > 400:
                        out_x = 0
                        out_y = out_y + ly
                    g2.addObject(layer)

                except Exception as e:
                    print "Caught exception:", repr(e)
        total_step += step
        current_z = start_z + total_step

    for section in sections:
        section.ViewObject.Visibility = False

    App.activeDocument().recompute()
My intention was to use this for lasercutting, so a sample run to split an object called "Solid" in FreeCAD into a cross-section every 4mm (to lasercut a part from 4mm plywood for example) would look like this:

Code: Select all

bisectCrossSections(App.ActiveDocument.Solid, 4)
Post Reply