Macro MoveToOrigin
| Description | 
|---|
| Moves the selected location of an object to the Origin. This macro translates the Placement of an object so that a selected location becomes its new origin. This may be useful when imported objects from other formats like STL or STEP are inconveniently located for further processing by FreeCAD. Macro version: 00.02 Last modified: 2021-07-07 Download: ToolBar Icon Author: Edwilliams16 | 
| Author | 
| Edwilliams16 | 
| Download | 
| ToolBar Icon | 
| Links | 
| Macros recipes How to install macros How to customize toolbars | 
| Macro Version | 
| 00.02 | 
| Date last modified | 
| 2021-07-07 | 
| FreeCAD Version(s) | 
| None | 
| Default shortcut | 
| None | 
| See also | 
| None | 
Description
This macro translates the Placement of an object so that a selected location becomes its new origin. This may be useful when imported objects from other formats like STL or STEP are inconveniently located for further processing by FreeCAD.
Usage
- Copy the macro into your Macro Directory. (You can copy and paste into the Macro Editor and save.)
- First select, then run the macro. Undo is available.
The following selections are supported:
-- Single Vertex -- moved to origin
-- Single Line Segment -- mid-point moved to origin
-- Single Circle or Circular Arc -- center moved to origin
-- Single Face -- C.G. moved to origin
-- Single Object -- C.G. moved to origin (supports Solid, Shell, Compound and CompSolid)
-- Three Vertices -- Center of circle through the vertices moved to the origin (useful for tessellated circles)
Macro MoveToOrigin.FCMacro
__Title__    = "MoveToOrigin"
__Author__   = "edwilliams16"
__Version__  = "00.02"
__Date__     = "2021.7.7"
debug = False
'''Usage:
This macro will adjust the placement of a shape to place a selected feature at the origin.
One vertex:  - to origin
One line segment - midpoint to origin
One circle - center to origin
One arc of circle - center to origin
One face - CG of face to origin
Three vertices - center of circle through vertices to origin
Shape - CG of shape to origin
If the shape is a BaseFeature inside a body, the base feature's placement in the body is adjusted.
'''
# Version 0.01  Original
# Version 0.02 - now handles objects in nested part containers
def circumcenter(A, B, C):
    '''Return the circumcenter of three 3D vertices'''
    a2 = (B - C).Length**2
    b2 = (C - A).Length**2
    c2 = (A - B).Length**2
    if a2 * b2 * c2 == 0.0:
        print('Three vertices must be distinct')
        return
    alpha = a2 * (b2 + c2 - a2)
    beta = b2 * (c2 + a2 - b2)
    gamma = c2 * (a2 + b2 - c2)
    return (alpha*A + beta * B + gamma * C)/(alpha + beta + gamma)
def centerofmass(subshapes):
    mass =0.0
    moment = App.Vector(0, 0, 0)
    for sh in subshapes:
        mass += sh.Volume
        moment += sh.Volume * sh.CenterOfMass
    return moment/mass  
def globalCG(obj, removeLocalOffset=True):
    '''Find COM of object in global coordinates
      remove local offset per https://forum.freecadweb.org/viewtopic.php?t=27821'''
    gpl = obj.getGlobalPlacement()
    pl = obj.Placement
    rpl= gpl.multiply(pl.inverse())
    if removeLocalOffset:
        return rpl.multVec(obj.Shape.CenterOfMass)
    else:
        return gpl.multVec(obj.Shape.CenterOfMass)
def globalGCGHierarchy(obj):
    ''' Compute global COM of an object hierarchy'''
    volume= 0
    moment = App.Vector(0., 0., 0.)
    def wrappedGCG(obj): 
        nonlocal volume, moment
        if hasattr(obj, 'Shape'):
            shape = obj.Shape
            if hasattr(shape, 'CenterOfMass'):
                print(f'{obj.Name}  {vectorRound(globalCG(obj, True), nDec)}')
                volume += shape.Volume
                moment += globalCG(obj, True).multiply(shape.Volume)
            else:
                for obj1 in obj.OutList:
                    if hasattr(obj1, 'Shape'):
                        wrappedGCG(obj1)
    wrappedGCG(obj)
    return (volume, moment)
def vectorRound(v, n):
    '''Round the output of App.Vector v to n dec places'''
    return App.Vector(round(v.x, n), round(v.y, n), round(v.z, n))
  
isGlobal = False
nDec = 3
selt = Gui.Selection.getSelectionEx()
if len(selt) != 1:
    print('Select one vertex, edge or object\nor three vertices for center of circle')
    shift = App.Vector(0, 0, 0)  # bail and do nothing  
else:
    sel = selt[0]
    if sel.HasSubObjects:
        vv = sel.SubObjects
        if len(vv) ==3 and vv[0].ShapeType == 'Vertex' and   vv[1].ShapeType == 'Vertex' and vv[2].ShapeType == 'Vertex':
            shift = circumcenter(vv[0].Point, vv[1].Point, vv[2].Point)
            if debug: print('Three Vertices')
        elif len(vv) == 1: 
            selected = sel.SubObjects[0]
            if selected.ShapeType == 'Vertex':
                shift = selected.Point
                if debug: print('One vertex')
            elif selected.ShapeType == 'Edge':
                if selected.Curve.TypeId == 'Part::GeomCircle':    #circles and arcs
                     shift = selected.Curve.Center
                     if debug: print('One arc')      
                elif selected.Curve.TypeId == 'Part::GeomLine':
                    shift = (selected.valueAt(selected.FirstParameter) + selected.valueAt(selected.LastParameter))/2 # mid-point
                    if debug: print('One line')
                else:
                    print(f'edge is {selected.Curve.TypeId} not handled')
                    shift = App.Vector(0, 0, 0)  # bail and do nothing
            elif selected.ShapeType == 'Face':
                shift = selected.CenterOfMass #center of face
                if debug: print('One face')
            else:
                print(f'ShapeType {selected.ShapeType} not handled')
                shift = App.Vector(0, 0, 0)  # bail and do nothing
        else:
            print('Wrong number of Selections')
            shift = App.Vector(0, 0, 0)  # bail and do nothing
    else:
        if debug: print(f'ObjectName is {sel.ObjectName}')
        #if sel.ObjectName[0:11] == 'BaseFeature':
        if hasattr(sel.Object, 'Shape'):
            if debug: print('One solid')
            shape = sel.Object.Shape
            if shape.ShapeType == 'Solid' or shape.ShapeType == 'Shell':
                shift = shape.CenterOfMass
            elif shape.ShapeType == 'Compound' or shape.ShapeType == 'CompSolid':
                #shift = centerofmass(shape.SubShapes)
                vol, mom = globalGCGHierarchy(sel.Object)
                shift = mom.multiply(1./vol)  # global!
                isGlobal = True
            #more cases?
            else:
                print(f'Object ShapeType {sel.Object.Shape.ShapeType} not handled')
                shift = App.Vector(0, 0, 0)  # bail and do nothing                
        else:
            print(f'{sel.ObjectName} is not a shape')
            shift = App.Vector(0, 0, 0)  # bail and do nothing
            
pl = App.ActiveDocument.getObject(sel.ObjectName).Placement
gpl = App.ActiveDocument.getObject(sel.ObjectName).getGlobalPlacement()
gplr = gpl.multiply(pl.inverse())
if isGlobal:
    shift = gplr.inverse().multVec(shift) # global shift in LCS
print(f'Shift = {vectorRound(shift,nDec)}')
locorigin = gplr.inverse().multVec(App.Vector(0., 0., 0.)) #global origin in LCS
App.ActiveDocument.openTransaction('Undo Move to Origin')
App.ActiveDocument.getObject(sel.ObjectName).Placement.move(locorigin-shift)
App.ActiveDocument.commitTransaction()
App.ActiveDocument.recompute()
Link
The discussion forum MoveToOrigin Macro
