Discussion on placement

Have some feature requests, feedback, cool stuff to share, or want to know where FreeCAD is going? This is the place.
Forum rules
Be nice to others! Read the FreeCAD code of conduct!
User avatar
Pauvres_honteux
Posts: 728
Joined: Sun Feb 16, 2014 12:05 am
Location: Far side of the moon

Re: Discussion on placement

Post by Pauvres_honteux »

... and regarding the "any geometry", it does happen one do want to Move just a point or line or curve and so on but like to have a the original left in its orignal place.

For all Moves we want the Moved stuff to be copies with new coordinates AND rememberd original position AND visualized with its own Move-icon in the tree.

This is what differs from todays setup.
palun
Posts: 1
Joined: Fri Sep 09, 2022 2:46 pm

Re: Discussion on placement

Post by palun »

edwilliams16 wrote: Tue Jun 15, 2021 8:06 pm

Code: Select all

debug = True
'''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.
'''

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    

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 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)
            #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
if debug: print(f'Shift = {shift}')
base = App.ActiveDocument.getObject(sel.ObjectName).Placement
base.move(-shift)
App.ActiveDocument.recompute()
The macro now handles ShapeTypes: Solid, Shell, Compound and CompSolid, computing the CG of composite objects.
It will use a single vertex, the mid-point of a line, the center of a circle or arc or the CG of a face as the reference point to move to the origin.
It will do nothing with B-splines and Wires.

There's no Undo - but the restoring shift is printed to the report view. Undo would be better if I figured how to do it.

Maybe approaching prime-time with user testing? Should I link to new thread in Python Scripting and Macros?

I made a few additions to the above code, with it you can select a second item and have the first item move to it. Good if you want to line up a vertex on one object with a vertex on another...

Code: Select all

'''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.
'''
from PySide import QtCore, QtGui
 
def err(msg):
    # Create a simple dialog QMessageBox
    # The first argument indicates the icon used: one of QtGui.QMessageBox.{NoIcon, Information, Warning, Critical, Question} 
    print('Error: '+ msg)
    diag = QtGui.QMessageBox(QtGui.QMessageBox.Warning, 'Error', msg)
    diag.setWindowModality(QtCore.Qt.ApplicationModal)
    diag.exec_()

def debug(msg):
    print(msg)

def calcVectorFromOrigin(A, B, C):
    '''Return the calcVectorFromOrigin 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:
        err('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 calcCenterOfMass(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 getShapeType(sel):
    if hasattr(sel, 'ShapeType'):
        return sel.ShapeType
    elif hasattr(sel, 'Object') and hasattr(sel.Object, 'Shape') and hasattr(sel.Object.Shape, 'ShapeType'):
        return sel.Object.Shape.ShapeType
    else:
        return 'unknown'


def getVectorFromOriginToSelection(sel):
    
    msg=f"Object '{sel.ObjectName}'"
    if sel.HasSubObjects:
        vv = sel.SubObjects
        msg=f"{msg} contained {vv} subobject(s)"
        if len(vv) ==3 and getShapeType(vv[0]) == 'Vertex' and   getShapeType(vv[1]) == 'Vertex' and getShapeType(vv[2]) == 'Vertex':
            debug(f"{msg} all with .ShapeType='Vertex', using their .Point properties")
            return calcVectorFromOrigin(vv[0].Point, vv[1].Point, vv[2].Point)
        elif len(vv) == 1: 
            selected = sel.SubObjects[0]
            shapeType=getShapeType(selected)
            msg=f"{msg} had .ShapeType='{shapeType}'"
            if shapeType == 'Vertex':
                debug(f"{msg}, using the .Point property")
                return selected.Point
            elif shapeType == 'Edge':
                msg=f"{msg} and .Curve.TypeId='{selected.Curve.TypeId}'"
                if selected.Curve.TypeId == 'Part::GeomCircle':    #circles and arcs
                     debug(f'{msg}, using the .Center property')      
                     return selected.Curve.Center
                elif selected.Curve.TypeId == 'Part::GeomLine':
                    debug(f'{msg}, using the average of the .FirstParameter and .LastParameter properties')
                    return (selected.valueAt(selected.FirstParameter) + selected.valueAt(selected.LastParameter))/2 # mid-point
                else:
                    err(f"{msg}, however currently only handling edges of types 'Part::GeomCircle' or 'Part::GeomLine'")
                    return App.Vector(0, 0, 0)  # bail and do nothing
            elif shapeType == 'Face':
                debug(f"{msg}, using the .CenterOfMass property")
                return selected.CenterOfMass #center of face
            else:
                err(f"{msg}. These types are not currently supported")
                return App.Vector(0, 0, 0)  # bail and do nothing
        else:
            err(f"{msg} which is not currently supported")
            return App.Vector(0, 0, 0)  # bail and do nothing
    else:
        # debug(f'ObjectName is {sel.ObjectName}')
        if hasattr(sel.Object, 'Shape'):
            shape = sel.Object.Shape
            msg=f"{msg} had .Object.Shape.ShapeType='{shape.ShapeType}'"
            if shape.ShapeType == 'Solid' or shape.ShapeType == 'Shell':
                debug(f"{msg}, using .Center property")
                return shape.CenterOfMass
            elif shape.ShapeType == 'Compound' or shape.ShapeType == 'CompSolid':
                debug(f"{msg}, calculating center or mass of its subshapes")
                return calcCenterOfMass(shape.SubShapes)
            elif shape.ShapeType == 'Vertex':
                debug(f"{msg}, using the .Point property")
                return shape.Point
            else:
                err(f"{msg}. These types are not currently supported")
                return App.Vector(0, 0, 0)  # bail and do nothing                
        else:
            err(f"{msg} doesn't have the .Shape property, ie. not supported")
            return App.Vector(0, 0, 0)  # bail and do nothing



debug('...')
selections = Gui.Selection.getSelectionEx()
s=len(selections)
if s==0 or s>2:
    err('Select one vertex, edge or object\nor three vertices for center of circle')
else:
    objName=selections[0].ObjectName
    obj=App.ActiveDocument.getObject(objName)
    if(s==1):
        debug(f"Will try to move {objName} to origin")
        vector=getVectorFromOriginToSelection(selections[0]);
        # Remember, the vector starts in the origin and runs to the current position, 
        # but we want the move to happen in reverse so...
        toorigin = -vector;
        debug(f'Vector to origin: {toorigin}')
        obj.Placement.move(toorigin)
    else:
        debug(f"Will try to move {objName} to second selection...")
        # techincally we're moving it first to the origin, then out to the second point
        vector = getVectorFromOriginToSelection(selections[0]);
        toorigin = -vector
        debug(f'Move to origin: {toorigin}')
        obj.Placement.move(toorigin)

        vector = getVectorFromOriginToSelection(selections[1]);
        fromorigin = vector
        debug(f'Move from origin to second selection: {fromorigin}')
        obj.Placement.move(fromorigin)


App.ActiveDocument.recompute()
edwilliams16
Veteran
Posts: 3180
Joined: Thu Sep 24, 2020 10:31 pm
Location: Hawaii
Contact:

Re: Discussion on placement

Post by edwilliams16 »

Sounds useful. Thanks, I'll take a look. A later version of my code, with undo and other features is in https://wiki.freecadweb.org/Macro_MoveToOrigin
I don't think it handles nested linked objects, because getGlobalPlacement() needs to be dealt with in that case. - though for its original purpose of dealing with STEP imports, this problem doesn't arise.
Post Reply