"Project on Surface" using Scripting.

Need help, or want to share a macro? Post here!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
User avatar
onekk
Veteran
Posts: 6205
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: "Project on Surface" using Scripting.

Post by onekk »

Chris_G wrote: Thu Jul 28, 2022 8:25 am Here is a script with explained workflow.
Thanks, now I'm facing two type of problems:

I have to extrude the face to make a relief, so i probably have to find the normal to project correctly the face obtained with the correct curvature.

1) for a cylinder it would be not a problem, but how to obtain a proper "extrude vector"?

2) When creating the face, as in the code below the "i" charcter is ok but the "g" is not, so I have to determine if one wire is internal to another so I could make one the "hole" of the other. I think that making an assumption that first wire is the "outer" wire, could lead to wrong results, but I could see some cases maybe in some nordic accented letters where there could be some characters with "holes" and optional part like the accent or other "decorations", so I'm wonder how to deal with these cases, in a more generic way?

I attached the modified code as a file to make more easy to work.


Below the portion of code

Code: Select all

mwrs = []

for s_idx, s_wire in enumerate(wire_s):
    wire_idx = f"m_wire_{s_idx}"
    if len(s_wire) > 1:
        subwrs = []        
        for sw_idx, sub_wire in enumerate(s_wire):
            mw = map_wire(sub_wire, surf.Surface, quad_face)
            wire_n = f"{wire_idx}_{sw_idx}"
            subwrs.append([wire_n, mw])
        mwrs.append(subwrs)
    else:
        mw = map_wire(s_wire[0], surf.Surface, quad_face)
        wire_n = wire_idx
        mwrs.append([wire_n, mw])

for mwr in mwrs:
    if type(mwr[0]) == str:
        face = Part.Face(mwr[1])
        Part.show(face, mwr[0])
    else:
        for smwr in mwr:
            face = Part.Face(smwr[1])
            Part.show(face, smwr[0])

20220727-text_on_surface.py
(5.25 KiB) Downloaded 29 times
TIA and 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/
User avatar
Chris_G
Veteran
Posts: 2598
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: "Project on Surface" using Scripting.

Post by Chris_G »

onekk wrote: Thu Jul 28, 2022 1:52 pm I have to extrude the face to make a relief, so i probably have to find the normal to project correctly the face obtained with the correct curvature.
1) for a cylinder it would be not a problem, but how to obtain a proper "extrude vector"?
For a general curved surface, the normal vector is not constant, so you can't find one normal vector.
In Sketch_On_Surface, I create an offset surface, and apply the same mapping of geom2d curves on it (the blue face, on the image below)
This way, I get the "other side" of the extrude.
And, for each geom2d curve, I create a ruled surface between the 2 mapped edges (the one on the target surface, and the one on the offset surface), to build the sides of the extrude. (the green faces, on the image below)
colors.jpg
colors.jpg (9.25 KiB) Viewed 1077 times
onekk wrote: Thu Jul 28, 2022 1:52 pm 2) When creating the face, as in the code below the "i" charcter is ok but the "g" is not, so I have to determine if one wire is internal to another so I could make one the "hole" of the other. I think that making an assumption that first wire is the "outer" wire, could lead to wrong results, but I could see some cases maybe in some nordic accented letters where there could be some characters with "holes" and optional part like the accent or other "decorations", so I'm wonder how to deal with these cases, in a more generic way?
If your letter is only a list of wires (not a face), I suppose you can compare the boundbox of each wire, to find the holes.
If your letter is a face, I use this this code snippet to sort the outerwire and the holes :

Code: Select all

ow = face.OuterWire
inner_wires = []
for w in face.Wires:
    if not w.isPartner(ow):
        inner_wires.append(w)

User avatar
onekk
Veteran
Posts: 6205
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: "Project on Surface" using Scripting.

Post by onekk »

Chris_G wrote: Thu Jul 28, 2022 2:57 pm For a general curved surface ...
Ok thanks, in guessed the way, I have already done similar thing.

I have simoly make two projection, but as the surface is not the same the UV mapping will differ, so probably I have to make a second BSpline and so on.
Chris_G wrote: Thu Jul 28, 2022 2:57 pm If your letter is only a list of wires (not a face) ...
I will see how to implement as faces are created after wites, so probably an early work with wires will be the way.

Thanks a lot I will experiment and post here the result.

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/
User avatar
onekk
Veteran
Posts: 6205
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: "Project on Surface" using Scripting.

Post by onekk »

Chris_G wrote: Thu Jul 28, 2022 2:57 pm ...
Ok I've elaborated some more the code, not too complicated, but I'm stuck with the problem of detecting the "holes".
20220727-text_on_surface.py
(6.64 KiB) Downloaded 26 times
I've supposed that wire[0] was the external wire, but this is not true as example for the q in the test code.

So I could not suppose that I have to check the wire[0] boundbox with all the remaining wires.

I could compare the wires, and see if maybe on is "enclosed" in another, but it seems that there is no a method to check quickly this condition, and it appears that a boundbox object has no methods to chek it against another boundbox this will make checking easier suppose that there is bb1.isInnner(BB2) or similar check.

Do you have some ideas?

TIA and 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/
User avatar
Chris_G
Veteran
Posts: 2598
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: "Project on Surface" using Scripting.

Post by Chris_G »

Code: Select all

help(FreeCAD.BoundBox.isInside)
User avatar
onekk
Veteran
Posts: 6205
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: "Project on Surface" using Scripting.

Post by onekk »

Chris_G wrote: Sat Jul 30, 2022 12:03 pm

Code: Select all

help(FreeCAD.BoundBox.isInside)
Thanks.

I have checked Part.Wire.BoundBox and found nothing :oops: ,

I have seen some code in Draft.shapestring, and I have seen that it use:

Code: Select all

   def makeGlyph(self, facelist):
        ''' turn list of simple contour faces into a compound shape representing a glyph '''
        ''' remove cuts, fuse overlapping contours, retain islands '''
        import Part
        if len(facelist) == 1:
            return(facelist[0])

        sortedfaces = sorted(facelist,key=(lambda shape: shape.Area),reverse=True)

        biggest = sortedfaces[0]
        result = biggest
        islands =[]
        for face in sortedfaces[1:]:
            bcfA = biggest.common(face).Area
            fA = face.Area
            difA = abs(bcfA - fA)
            eps = utils.epsilon()
            # if biggest.common(face).Area == face.Area:
            if difA <= eps:                              # close enough to zero
                # biggest completely overlaps current face ==> cut
                result = result.cut(face)
            # elif biggest.common(face).Area == 0:
            elif bcfA <= eps:
                # island
                islands.append(face)
            else:
                # partial overlap - (font designer error?)
                result = result.fuse(face)
        #glyphfaces = [result]
        wl = result.Wires
        for w in wl:
            w.fixWire()
        glyphfaces = [Part.Face(wl)]
        glyphfaces.extend(islands)
        ret = Part.Compound(glyphfaces) # should we fuse these instead of making compound?
        return ret
And seems promising, as it sort the faces so probably those with a big area is the "outerface" let's try to make some similar code, I don't know if there are characters with "decorations and holes" in the same character, for at least for English, Italian and French (probably even with German) I could not see such difficult characters, so I think that it suffice, for now. (hoping to make a working example).

Thanks again.

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/
User avatar
onekk
Veteran
Posts: 6205
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: "Project on Surface" using Scripting.

Post by onekk »

Some more work toward the goal:
20220727-text_on_surface.py
(8.15 KiB) Downloaded 28 times
Now I have the two set of faces:
tx_on_surf.png
tx_on_surf.png (9.29 KiB) Viewed 888 times
And I have tought to use a makePipeShell approach, but probably I'm wrong as it could not deal with faces, plus I have some problems in the shapes, as I obtain some errors during the computations.

EDIT: I found that the error is arising when the character is a g but strangeky enough using the Python Console it is possible to compute the desired data:

Code: Select all

Traceback (most recent call last):
File ".../Forum/20220727-text_on_surface.py", line 304, in <module>
   pt1 = element[0].CenterOfMass
<class 'AttributeError'>: 'Part.Shape' object has no attribute 'CenterOfMass'
END EDIT

If I return from string I have to make a sort of list that will contain, outer wire, and probably inner wires so I could make an outer Loft and an inner Loft that has to be subtracted from the outer loft but this seem an overcomplication, any idea?

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/
User avatar
Chris_G
Veteran
Posts: 2598
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: "Project on Surface" using Scripting.

Post by Chris_G »

Or you can build all the ruled surfaces that join the 2 offset letters, to close the solid.
User avatar
onekk
Veteran
Posts: 6205
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: "Project on Surface" using Scripting.

Post by onekk »

Chris_G wrote: Sat Jul 30, 2022 4:10 pm Or you can build all the ruled surfaces that join the 2 offset letters, to close the solid.
Ok I have done some code, but I have some problem, as for hollow characters something is not working, probably some order when making shell:
tx_on_surf_pbl.png
tx_on_surf_pbl.png (18.73 KiB) Viewed 826 times
EDIT some more images
tx_on_surf_pbl1.png
tx_on_surf_pbl1.png (33.94 KiB) Viewed 808 times
tx_on_surf_pbl2.png
tx_on_surf_pbl2.png (8.49 KiB) Viewed 808 times
The following portion of code work till letter number 5, so the g is creating problem.

Code: Select all

cnt = 0
for element in zip(faces1, faces2):
    edg0 = element[0].Edges
    edg1 = element[1].Edges
    
    surfaces = [element[0]]    
    for e_idx, edge in enumerate(edg1):
        surf = Part.makeRuledSurface(edg0[e_idx], edg1[e_idx])
        surfaces.append(surf)
        # Part.show(surf, f"element_{cnt}_surface_{e_idx}")

    surfaces.append(element[1])
    shell = Part.Shell(surfaces)
    solid = Part.Solid(shell)
    Part.show(solid, f"letter_{cnt}")

    cnt += 1 
    
Do you have an idea or there is a way to "order the surfaces" passed to makeshell to correctly close the shape?


TIA and Regards.

Carlo D.
20220727-text_on_surface.py
(8.2 KiB) Downloaded 27 times
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/
User avatar
mfro
Posts: 666
Joined: Sat Sep 23, 2017 8:15 am

Re: "Project on Surface" using Scripting.

Post by mfro »

onekk wrote: Sat Jul 30, 2022 4:57 pm Do you have an idea or there is a way to "order the surfaces" passed to makeshell to correctly close the shape?
Your Loft approach wasn't that bad, you however need to take account of the different possible topologies returned:
  1. if not hollow, you receive a Face
  2. if one single hole, you receive a Shell
  3. if multiple holes, you receive a Compound
You just need to extract the wires from these, make lofts and cut the holes from the outer solid.
string.png
string.png (119.8 KiB) Viewed 754 times
Attachments
TextonFace.FCMacro
(9.39 KiB) Downloaded 30 times
Cheers,
Markus
Post Reply