Part translation not applying / possible bug

Need help, or want to share a macro? Post here!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
Jyers
Posts: 5
Joined: Sat May 28, 2022 1:35 pm

Part translation not applying / possible bug

Post by Jyers »

Hello!

I'm relatively new to FreeCAD and this is my first post on the form. I'm looking to learn so let me know if I'm missing something.

I'm doing most of my work with python scripting and so far it's going great. However I'm having a very strange issue with part translation in one of my featurePython parts.

Here's the execute function from its class:

Code: Select all

def execute(self, obj):

        dx = HEAD_DIA_8_32+3
        dy = obj.MountHoleDistance.Value + CLR_DIA_8_32*2 + 2
        dz = HEAD_DZ_8_32+3
        part = Part.makeBox(dx, dy, dz)
        part.translate(App.Vector(-dx/2, -dy/2, -dz))
        for i in part.Edges:
            if i.tangentAt(i.FirstParameter) == App.Vector(0, 0, 1):
                part = part.makeFillet(4, [i])
        temp = Part.makeCylinder(CLR_DIA_8_32/2, dz-HEAD_DZ_8_32, App.Vector(0, 0, 0), App.Vector(0, 0, -1))
        temp = temp.fuse(Part.makeCylinder(HEAD_DIA_8_32/2, HEAD_DZ_8_32, App.Vector(0, 0, HEAD_DZ_8_32-dz), App.Vector(0, 0, -1)))
        part = part.cut(temp)
        temp.rotate(App.Vector(0, 0, -dz/2), App.Vector(0, 1, 0), 180)
        temp.translate(App.Vector(0, -obj.MountHoleDistance.Value/2, 0))
        part = part.cut(temp)
        temp.translate(App.Vector(0, obj.MountHoleDistance.Value, 0))
        part = part.cut(temp)
        part.translate(App.Vector(5, 5, 5))
        obj.Shape = part
The issue is that the "part.translate(App.Vector(5, 5, 5))" line is not getting applied when it's at the end like it is here.
I've done a fair bit of testing around to figure out exactly what works and what doesn't. If I put that command up one line above the cut command, or if I put something like the for loop under it it works.
If I simplify the script to just the makeBox and translate command it works, but if I add back and of the cut commands or the for loop it doesn't.

So this works as expected:

Code: Select all

def execute(self, obj):

        dx = HEAD_DIA_8_32+3
        dy = obj.MountHoleDistance.Value + CLR_DIA_8_32*2 + 2
        dz = HEAD_DZ_8_32+3
        part = Part.makeBox(dx, dy, dz)
        part.translate(App.Vector(-dx/2, -dy/2, -dz))
        part.translate(App.Vector(5, 5, 5))
        obj.Shape = part
but this fails to apply the translate:

Code: Select all

def execute(self, obj):

        dx = HEAD_DIA_8_32+3
        dy = obj.MountHoleDistance.Value + CLR_DIA_8_32*2 + 2
        dz = HEAD_DZ_8_32+3
        part = Part.makeBox(dx, dy, dz)
        part.translate(App.Vector(-dx/2, -dy/2, -dz))
        temp = Part.makeCylinder(CLR_DIA_8_32/2, dz-HEAD_DZ_8_32, App.Vector(0, 0, 0), App.Vector(0, 0, -1))
        temp = temp.fuse(Part.makeCylinder(HEAD_DIA_8_32/2, HEAD_DZ_8_32, App.Vector(0, 0, HEAD_DZ_8_32-dz), App.Vector(0, 0, -1)))
        part = part.cut(temp)
        part.translate(App.Vector(5, 5, 5))
        obj.Shape = part
I think this very well could just be a bug but perhaps I'm missing something here. Any help / testing to recreate this would be greatly appreciated :D
User avatar
Roy_043
Veteran
Posts: 8450
Joined: Thu Dec 27, 2018 12:28 pm

Re: Part translation not applying / possible bug

Post by Roy_043 »

I can't reproduce with this test code:

Code: Select all

import FreeCAD as App
import Part

doc = App.newDocument()

obj = Part.makeBox(10, 20, 30)
obj.translate(App.Vector(-5, -10, -30))
temp = Part.makeCylinder(8, 30, App.Vector(0, 0, 0), App.Vector(0, 0, -1))
obj = obj.cut(temp)
obj.translate(App.Vector(5, 5, 5))
new = doc.addObject("Part::Feature", "Test")
new.Shape = obj
Jyers
Posts: 5
Joined: Sat May 28, 2022 1:35 pm

Re: Part translation not applying / possible bug

Post by Jyers »

Seems like it may be isolated to featurePython objects

Here's a concise macro that demonstrates the issue using your code but put into a featurePython class:

Code: Select all

import FreeCAD as App
import Part

class testing:

    def __init__(self, obj):

        obj.Proxy = self

    def execute(self, obj):
        part = Part.makeBox(10, 20, 30)
        part.translate(App.Vector(-5, -10, -30))
        temp = Part.makeCylinder(8, 30, App.Vector(0, 0, 0), App.Vector(0, 0, -1))
        part = part.cut(temp)
        part.translate(App.Vector(5, 5, 5))
        obj.Shape = part

class ViewProvider:

    def __init__(self, obj):
        obj.Proxy = self

    def attach(self, obj):
        return

    def updateData(self, fp, prop):
        return

    def getDisplayModes(self,obj):
        return []

    def getDefaultDisplayMode(self):
        return "Shaded"

    def setDisplayMode(self,mode):
        return mode

    def onChanged(self, vp, prop):
        App.Console.PrintMessage("Change property: " + str(prop) + "\n")

    def getIcon(self):
        return 

    def __getstate__(self):
        return None

    def __setstate__(self,state):
        return None

obj = App.ActiveDocument.addObject("Part::FeaturePython", "Test")
testing(obj)
ViewProvider(obj.ViewObject)
App.ActiveDocument.recompute()
User avatar
Roy_043
Veteran
Posts: 8450
Joined: Thu Dec 27, 2018 12:28 pm

Re: Part translation not applying / possible bug

Post by Roy_043 »

I just remembered a previous discussion:
https://www.forum.freecadweb.org/viewto ... 72#p566072

What you are seeing is by design apparently.
User avatar
onekk
Veteran
Posts: 6146
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: Part translation not applying / possible bug

Post by onekk »

Hello, this will work, at least in 0.20.R28810

Code: Select all


    def execute(self, obj):
        part = Part.makeBox(10, 20, 30)
        part.translate(App.Vector(-5, -10, -30))
        temp = Part.makeCylinder(8, 30, App.Vector(0, 0, 0), App.Vector(0, 0, -1))
        part = part.cut(temp).removeSplitter()
        displ = App.Vector(50, 50, 50)
        obj.Shape = part
        obj.Placement  = App.Placement(displ, App.Rotation(0, 0, 0))
Probably the Shape is copying only the Base in other word the original shape "without properties".

Such behaviour it is not unusual in FC, you could, usually a boolean operation will "consolidate" his placement, but you have to "position components" before to do the cut.

I use this trick very often, position the objects in the wanted position before doing the "final boolenan operation".

Maybe I'm guessing wrong, but when you do a "boolean operation", you have to position components relatively to each other, and when you do the "operation" your "final shape" will be "stored" with components in "final position", so there is no "placement" other than the position of the topological components, like edges vertexes and curves.

This seems to be confirmed by the last sentence of the tansformGeometry() documentation that you will obtain using in the Python Console:

Code: Select all

print(Part.Shape.transformGeometry.__doc__)

Apply geometric transformation on this or a copy the shape.
transformGeometry(matrix) -> Shape
--
This method returns a new shape.
The transformation to be applied is defined as a 4x4 matrix.
The underlying geometry of the following shapes may change:
- a curve which supports an edge of the shape, or
- a surface which supports a face of the shape;

For example, a circle may be transformed into an ellipse when
applying an affinity transformation. It may also happen that
the circle then is represented as a B-spline curve.

The transformation is applied to:
- all the curves which support edges of the shape, and
- all the surfaces which support faces of the shape.

Note: If you want to transform a shape without changing the
underlying geometry then use the methods translate or rotate.
see maybe also the doc for Part.Shape.transformShape

NOTE: these "docstrings" are from 0.20R28810.

EDIT: from the discussion posted by Roy_43 it seems to confirm my thinking, but probably some test has to be done, or probably some more authorative answers (@wmayer) should make some more light.
wmayer wrote: Fri Jan 28, 2022 3:38 pm
Sorry for poking, but this is a similar case?

Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
Jyers
Posts: 5
Joined: Sat May 28, 2022 1:35 pm

Re: Part translation not applying / possible bug

Post by Jyers »

Interestingly enough, transformGeometry and transformShape both have the same issue.

This code demonstrates:

Code: Select all

import FreeCAD as App
import Part

class testing:

    def __init__(self, obj):

        obj.Proxy = self

    def execute(self, obj):
        part = Part.makeBox(10, 20, 30)
        part.translate(App.Vector(-5, -10, -30))
        temp = Part.makeCylinder(8, 30, App.Vector(0, 0, 0), App.Vector(0, 0, -1))
        part = part.cut(temp)
        mat = App.Matrix()
        mat.move(App.Vector(5, 5, 5))
        part.transformGeometry(mat)
        obj.Shape = part

class ViewProvider:

    def __init__(self, obj):
        obj.Proxy = self

    def attach(self, obj):
        return

    def updateData(self, fp, prop):
        return

    def getDisplayModes(self,obj):
        return []

    def getDefaultDisplayMode(self):
        return "Shaded"

    def setDisplayMode(self,mode):
        return mode

    def onChanged(self, vp, prop):
        App.Console.PrintMessage("Change property: " + str(prop) + "\n")

    def getIcon(self):
        return 

    def __getstate__(self):
        return None

    def __setstate__(self,state):
        return None

obj = App.ActiveDocument.addObject("Part::FeaturePython", "Test")
testing(obj)
ViewProvider(obj.ViewObject)
App.ActiveDocument.recompute()
It seems without a "final boolean operation" all of these translations simply get seen by freecad as placement changes which get overwritten
Jyers
Posts: 5
Joined: Sat May 28, 2022 1:35 pm

Re: Part translation not applying / possible bug

Post by Jyers »

Oops my bad, with part = part.transformGeometry(mat), it works as expected
wmayer
Founder
Posts: 20243
Joined: Thu Feb 19, 2009 10:32 am
Contact:

Re: Part translation not applying / possible bug

Post by wmayer »

Oops my bad, with part = part.transformGeometry(mat), it works as expected
Be careful with transformGeometry()! It internally uses OCC's BRepBuilderAPI_GTransform algorithm that converts all underlying geometries into B-Spline curves and surfaces. If you don't want that you have to stay with translate().

To make your original example working all what you have to do is setting the object's placement before assigning the shape. See the additional line

Code: Select all

obj.Placement = part.Placement
Full example:

Code: Select all

import FreeCAD as App
import Part

class testing:
    
    def __init__(self, obj):

        obj.Proxy = self
    
    def execute(self, obj):
        part = Part.makeBox(10, 20, 30)
        part.translate(App.Vector(-5, -10, -30))
        temp = Part.makeCylinder(8, 30, App.Vector(0, 0, 0), App.Vector(0, 0, -1))
        part = part.cut(temp)
        part.translate(App.Vector(5, 5, 5))
        obj.Placement = part.Placement
        obj.Shape = part

class ViewProvider:
    
    def __init__(self, obj):
        obj.Proxy = self
    
    def attach(self, obj):
        return
    
    def updateData(self, fp, prop):
        return
    
    def getDisplayModes(self,obj):
        return []
    
    def getDefaultDisplayMode(self):
        return "Shaded"
    
    def setDisplayMode(self,mode):
        return mode
    
    def onChanged(self, vp, prop):
        App.Console.PrintMessage("Change property: " + str(prop) + "\n")
    
    def getIcon(self):
        return 
    
    def __getstate__(self):
        return None
    
    def __setstate__(self,state):
        return None


obj = App.ActiveDocument.addObject("Part::FeaturePython", "Test")
testing(obj)
ViewProvider(obj.ViewObject)
App.ActiveDocument.recompute()
User avatar
onekk
Veteran
Posts: 6146
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: Part translation not applying / possible bug

Post by onekk »

Jyers wrote: Sun May 29, 2022 1:52 pm Oops my bad, with part = part.transformGeometry(mat), it works as expected
I hope that for the 1.0 version (That seems to be officially next release after 0.20) some documentation will be made, it is a pity that many relevant information are left to some @wmayer posts difficult to find if not saved and noted, as the recent @wmayer post is demostrating.

For now some relevant information could be retrieved using as example in Python Console:

Code: Select all

print(something.__doc__)

or better

help(something)
See below for a result.

>>> import Part
>>> help(Part.Shape)
Help on class Shape in module Part:

class Shape(Data.ComplexGeoData)
| TopoShape is the OpenCasCade topological shape wrapper.
| Sub-elements such as vertices, edges or faces are accessible as:
| * Vertex#, where # is in range(1, number of vertices)
| * Edge#, where # is in range(1, number of edges)
| * Face#, where # is in range(1, number of faces)
|
| Method resolution order:
| Shape
| Data.ComplexGeoData
| Base.Persistence
| Base.BaseClass
| builtins.PyObjectBase
| builtins.object
|
| Methods defined here:
|
| __delattr__(self, name, /)
| Implement delattr(self, name).
|
...

EDIT: Take in account that the output has a limit in term of characters or lines so not all the output of the help command is shown, probably better is to use something similar that will produce and HTML file name Shape.html in home directory:

Code: Select all

import pydoc
pydoc.writedoc(Part.Shape)
Links are not wokring, but the relevant infos are here.


NOTE: I've edited this multiple times, sorry for the mess.

Sorry for the verbosity and hoping it will help.

Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
Jyers
Posts: 5
Joined: Sat May 28, 2022 1:35 pm

Re: Part translation not applying / possible bug

Post by Jyers »

wmayer wrote: Sun May 29, 2022 4:05 pm To make your original example working all what you have to do is setting the object's placement before assigning the shape. See the additional line
Thank you. I did try that however and it does not lead to the result I'm looking for. Assigning the placement before setting the shape like that sets the placement to the last translations value, where in my case I want to translate the part and leave the placement zeroed. For that there seem to be only two options. Either use a extra boolean operation to reset the placement after the translation (part = part.fuse(part)) or use transformGeometry to avoid placement all together
Post Reply