FreeCAD icon indicating copy to clipboard operation
FreeCAD copied to clipboard

Using the scaling with `App.Vector(-1,-1,-1)` to mirror objects deforms the appearance of the object.

Open leoheck opened this issue 8 months ago • 28 comments

Is there an existing issue for this?

  • [x] I have searched the existing issues

Problem description

Using the scaling with App.Vector(-1, -1, -1) to mirror objects deforms the appearance of the object.

The object on the left was mirrored to the left, and the colors were messed up.

Image

Discussion: https://forum.freecad.org/viewtopic.php?p=818394#p818394

Full version info

0.21, 1.0, 1.1-dev

Subproject(s) affected?

Core

Anything else?

No response

Code of Conduct

  • [x] I agree to follow this project's Code of Conduct

leoheck avatar Mar 25 '25 17:03 leoheck

This is probably due to due to inverted normals. Scaling with -1 should in general be avoided in 3d applications.

hyarion avatar Mar 25 '25 18:03 hyarion

Do you know a way to fix this after the mirror happens?

leoheck avatar Mar 25 '25 18:03 leoheck

You are basically turning it inside out. Why do you need to mirror fasteners instead of just placing them at the right places?

hyarion avatar Mar 25 '25 20:03 hyarion

I am not mirroring fasteners; I am mirroring objects, and fasteners are one of them. I have an object in one location, I have a plane, and then I am mirroring the object based on this plane. But I am not doing this. This is a feature of a Workbench. It does not behave nicely, so I am trying to understand how I can fix it so it does not produce this issue.

leoheck avatar Mar 25 '25 20:03 leoheck

Scaling is the wrong way to place objects. I've done it myself and felt pretty smart about a hack like that in the past, but it always comes and bites me in the butt with threads reversing and normals pointing the wrong direction.

I would argue that scaling with negative values are undefined at best and would be good to throw an exception when the user tries to do it.

It would be good if you file this as a bug in that workbench.

hyarion avatar Mar 26 '25 05:03 hyarion

Scaling is the wrong way to place objects.

I see.

It would be good if you file this as a bug in that workbench.

I did. But I am trying to fix it myself somehow.

Someone in the Freecad Forum (link in the first message of the post, above) mentioned it could be a bug, so I reported it here.

leoheck avatar Mar 26 '25 12:03 leoheck

They mentioned this, and I agree.

Yes, from my perspective. If you are scaling an object, this should not change the position of the light source. Even if that scaling is negative, the programmer should account for this. If they cannot, or refuse to, then they should disallow negative scaling all together.

leoheck avatar Mar 26 '25 12:03 leoheck

But other one said that:

The mirroring has no impact on the light source. The light source is part of the viewer logic and objects of a document don't change anything there.

The problem is basically a mathematical thing because using the scale factor of -1 for all coordinates one ends up with a 3x3 transformation matrix with the determinant of -1. This means that the orientation of the object is changed and surface normals do not point to the outside but to the inside.

The effect is that the rendering shows the observed behaviour.

leoheck avatar Mar 26 '25 12:03 leoheck

It seems that enabling the backlight in the settings can "improve" the visual quality of the resulting object. However, I am not sure this is the right fix.

leoheck avatar Mar 30 '25 18:03 leoheck

No, the right fix is to avoid scaling using negative numbers.

hyarion avatar Mar 30 '25 18:03 hyarion

This approach works on 0.21.3, but in 1.1 the back light is already enabled.

leoheck avatar Mar 30 '25 18:03 leoheck

No, the right fix is to avoid scaling using negative numbers.

Do you have an idea to help me fixing this?

leoheck avatar Mar 30 '25 18:03 leoheck

So we're holding it wrong? Scaling with negative numbers IS mirroring. If FreeCAD messes up the normals, that's a bug that should be fixed.

jffmichi avatar Apr 12 '25 06:04 jffmichi

Hi, Mirroring a component with Scale = -1 was working some time ago [1]. I don't know the details but it is a useful feature of LinkPart [2]. I was using it in an assembly but now the behaviour has changed: not only changes colour but it doesn't really mirror the component any more. See screencast below. All components has been inserted with the integrated assembly's "insert" tool. It works properly with appimage 39708 (see the second screencast) but it wasn't working with appimage 41127 (~March 2025).

If not, what is the correct way to obtain a mirrored part, @hyarion ? The issue is described here and here and they are still unsolved.

[1] https://github.com/FreeCAD/FreeCAD/issues/14623 [2] https://forum.freecad.org/viewtopic.php?style=3&p=555675#p555675

Not working with appimage 41213: https://github.com/user-attachments/assets/43450e25-1475-46a5-9a89-46853af8a296

Example files: issue 20464 Mirroring.zip

Working in appimage 39708: https://github.com/user-attachments/assets/e6d73a1e-3627-4218-a612-c57c70a9ea16

OS: Devuan GNU/Linux 5 (daedalus) (XFCE/xfce/xcb) Architecture: x86_64 Version: 1.1.0dev.41213 (Git) Conda AppImage Build date: 2025/04/09 15:48:15 Build type: Release Branch: main Hash: f258a2639cd8acd45cb101b7f34e90e603d91696 Python 3.11.11, Qt 6.7.3, Coin 4.0.3, Vtk 9.3.1, IfcOpenShell 0.8.1, OCC 7.8.1 Locale: Catalan/Spain (ca_ES) Stylesheet/Theme/QtStyle: FreeCAD Light.qss/FreeCAD Light/ Logical DPI/Physical DPI/Pixel Ratio: 96/93.8695/1.07292

MiqCG avatar Apr 12 '25 08:04 MiqCG

PS: now I think that maybe I have to open a new issue for this, as it is related with the integrated assembly, and not (only) with Mod: Part and (only) with Topic: Color. Let me know, @hyarion , and I will do it.

MiqCG avatar Apr 12 '25 09:04 MiqCG

If not, what is the correct way to obtain a mirrored part, @hyarion ?

Yes this is a problem and I don't know a good way to do it currently:

  • Part_Mirror would be one way, but that doesn't support mirroring App_Part
  • Subshape binder + PartDesign_Mirrored would be another way, but PD_Mirrored doesn't support replacing original, only adding.

So I think your problem is a new issue: There's currently no way to mirror a part (that I know about) and this isn't only for assembly, another usecase would be when you want to create two similar mirrored parts, then there's no way I can find to do that.

hyarion avatar Apr 12 '25 11:04 hyarion

Not a new issue because it is already reported:

The issue is described here and here and they are still unsolved.

But, what about the possibility to use Scale = -1 that was working at least until March? (I don't have more appimages in between 39708 and 41127. I think it is the same or part of this original issue that has reported leoheck.

@leoheck , can you chech if, apart from the color, the mirroring works on your side? Please, test it with a non-symmetrical component. If that is the case, maybe you could update the title and the description.

MiqCG avatar Apr 12 '25 14:04 MiqCG

@MiqCG there are some assymetric objects reported on the following link. They mirror as expected except by the color.

https://github.com/Zolko-123/FreeCAD_Assembly4/issues/552

If you follow the link above you can see that on previous versions of Freecad, enabling the background light in the settings used to fix this problem. But now, on 1.1 this is already on however the issue still persists.

leoheck avatar Apr 12 '25 15:04 leoheck

See this example here: mirror_issue.FCStd.zip

Here, I have mirrored the object 2 times. Image

leoheck avatar Apr 12 '25 16:04 leoheck

@MiqCG do you need an appimage? I have these saved:

FreeCAD_weekly-builds-41213-conda-Linux-x86_64-py311.AppImage
FreeCAD_weekly-builds-41138-conda-Linux-x86_64-py311.AppImage
FreeCAD_weekly-builds-41127-conda-Linux-x86_64-py311.AppImage
FreeCAD_weekly-builds-40971-conda-Linux-x86_64-py311.AppImage
FreeCAD_weekly-builds-40655-conda-Linux-x86_64-py311.AppImage
FreeCAD_weekly-builds-40444-conda-Linux-x86_64-py311.AppImage
FreeCAD_weekly-builds-40408-conda-Linux-x86_64-py311.AppImage
FreeCAD_weekly-builds-38553-conda-Linux-x86_64-py311.AppImage
FreeCAD_weekly-builds-36438-2024-03-15-conda-Linux-x86_64-py310.AppImage

leoheck avatar Apr 12 '25 16:04 leoheck

I have downloaded your file but it involves Assembly4. This part that you are mirroring don't have a different form when mirrored and when not. On the other hand, we are using a different approach to mirror an object. Am I messing up talking about scale = -1 in here? How is App.Vector(-1,-1,-1) applied?

I have made more tests: if I link the Original_Arm.FCStd of my example to another file (without Assembly container) the issue arises too:

  1. open the Original_Arm file
  2. open a new file and save the file
  3. link the object to the new file with Make link
  4. apply scale = -1 in the properties panel (the mirrored part appears)
  5. transforming (moving) the object make disappear the mirroring and the object turns blackSo, it is not exclusive of the integrated assembly WB.

@leoheck , can you download my example files and test the mirroring as described? Depending on the result, I will ask you for appimages to find the moment the problem arouse.

MiqCG avatar Apr 12 '25 17:04 MiqCG

FYI the Link double-side rendering is checked here: https://github.com/FreeCAD/FreeCAD/blob/e990b456b9db3952cf785cb38619622415b13dca/src/Gui/ViewProviderLink.cpp#L1848-L1864

FlachyJoe avatar Apr 12 '25 21:04 FlachyJoe

@MiqCG Asm4 does this (I removed some irrelevant parts)

class makeMirrorArray(makeExpressionArray):

    def _setupProperties(self, obj):
        obj.Count = 2
        obj.setExpression('Scaler', '1 - 2 * (Index % 2)')
        obj.setExpression('.Placer.Base', '.Placer.Rotation * minvert(.AxisPlacement) * .SourceObject.Placement.Base * -2 * (Index % 2)')
        obj.setExpression('.Placer.Rotation.Angle', '180 * (Index % 2)')

class ExpressionArray(LinkArray):

    # do the calculation of the elements Placements
    def execute(self, obj):

        """ The placement is calculated relative to the axis placement
        Without Axis the Array is relative to the internal Z axis of the SourceObject."""

        sObj = obj.SourceObject
        # we only deal with objects that are in a parent container because we'll put the array there
        parent = sObj.getParentGeoFeatureGroup()
        
        if obj.Axis:
            obj.AxisPlacement = findAxisPlacement(*obj.Axis)
        else:
            obj.AxisPlacement = obj.SourceObject.Placement


        pmt1 = obj.AxisPlacement.inverse() * sObj.Placement

        placementList = []
        scaleList = []
        expDict = dict(obj.ExpressionEngine)
        evalList = _evalOrder(expDict)

        # calculate placement of each element
        for i in range(obj.Count):
            obj.Index = i
            for pn in evalList:
                ps = 'obj.'+pn.lstrip('.')
                nv = type(eval(ps))(obj.evalExpression(expDict[pn]))
                o,a = ps.rsplit('.',1)
                if a == 'Angle' and type(eval(o)) == App.Rotation:
                    nv = radians(nv)
                # must set entire rotation axis at once.
                # Related discussion: https://forum.freecad.org/viewtopic.php?t=73898
                if o.endswith('.Axis') and a in 'xyz':
                    axv = eval(o.replace('.Axis','.RawAxis'))
                    setattr(axv, a, nv)
                    exec(o + ' = axv')
                    continue
                exec(ps + ' = nv')
            placementList.append(obj.AxisPlacement * obj.Placer * pmt1)
            s = obj.Scaler
            scaleList.append(App.Vector(s, s, s)) #<---------------------- This is (-1, -1, -1) when obj.Scaler = -1
  
        # Resetting Index to 1 because we get more useful preview results 
        # in the expression editor
        obj.Index = 1
        if obj.ShowElement:
            for i in range(obj.Count):
                el = obj.ElementList[i]
                el.NoTouch = True
                el.Placement = placementList[i]
                el.ScaleVector = scaleList[i]
                el.setPropertyStatus('Placement',   'ReadOnly')
                el.setPropertyStatus('ScaleVector', 'ReadOnly')
                el.setPropertyStatus('Scale',       'ReadOnly')
                el.NoTouch = False
        else:
            obj.PlacementList = placementList
            obj.ScaleList = scaleList
        return

leoheck avatar Apr 13 '25 03:04 leoheck

But I confirm that your example @MiqCG reproduces the issue.

Check here:

https://github.com/user-attachments/assets/507ab1f3-2d43-4855-a8b1-c8d38a0cd072

leoheck avatar Apr 13 '25 03:04 leoheck

Sorry but I don't have enough programming skills to fully understand what the code that you sent really does.

Thanks for the testing. I will open another issue with this problem to not distort yours.

Can you give me access to some of this appimages? FreeCAD_weekly-builds-40971-conda-Linux-x86_64-py311.AppImage FreeCAD_weekly-builds-40655-conda-Linux-x86_64-py311.AppImage FreeCAD_weekly-builds-40444-conda-Linux-x86_64-py311.AppImage FreeCAD_weekly-builds-40408-conda-Linux-x86_64-py311.AppImage

We can make it one by one, beginning with the oldest one. I just want to find the moment, to ease the finding of the problematic code change.

MiqCG avatar Apr 13 '25 09:04 MiqCG

New issue created: https://github.com/FreeCAD/FreeCAD/issues/20776 Thanks and sorry for the inconvenience.

MiqCG avatar Apr 13 '25 09:04 MiqCG

@MiqCG, I think it is the same problem.

I am putting my AppImages here. https://www.dropbox.com/scl/fo/kk8ffuxc15ioj5l2b9r32/ABUUCxxigx1nlgVQMaPhdG8?rlkey=60qkhu7fwaxlh0m5p6jeaeonx&dl=0

It will take some time to upload them, but these are all the files you will find there

FreeCAD_weekly-builds-36438-2024-03-15-conda-Linux-x86_64-py310.AppImage
FreeCAD_weekly-builds-38553-conda-Linux-x86_64-py311.AppImage
FreeCAD_weekly-builds-40408-conda-Linux-x86_64-py311.AppImage
FreeCAD_weekly-builds-40444-conda-Linux-x86_64-py311.AppImage
FreeCAD_weekly-builds-40655-conda-Linux-x86_64-py311.AppImage
FreeCAD_weekly-builds-40971-conda-Linux-x86_64-py311.AppImage
FreeCAD_weekly-builds-41127-conda-Linux-x86_64-py311.AppImage
FreeCAD_weekly-builds-41138-conda-Linux-x86_64-py311.AppImage
FreeCAD_weekly-builds-41213-conda-Linux-x86_64-py311.AppImage
FreeCAD_weekly-builds-41232-conda-Linux-x86_64-py311.AppImage

leoheck avatar Apr 13 '25 18:04 leoheck

soft bump

luzpaz avatar May 09 '25 21:05 luzpaz

The related issue here https://github.com/FreeCAD/FreeCAD/issues/20776#issuecomment-2993799518 has been fixed. However, this one has not been resolved yet. Something else has broken the color of the mirrored object when using Asm4, but I don’t know what yet.

leoheck avatar Jun 23 '25 03:06 leoheck

https://github.com/FreeCAD/FreeCAD/issues/22170#comment-composer-heading

leoheck avatar Jun 25 '25 00:06 leoheck