Generative Design with FreeCAD

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!
paullee
Veteran
Posts: 5098
Joined: Wed May 04, 2016 3:58 pm

Re: Generative Design with FreeCAD

Post by paullee »

j8sr0230 wrote: Sun May 29, 2022 10:02 am I like your implementation and to be honest I initially took exactly the same approach with two classes to position link array objects in space. However, I wanted my LinkArray to behave and look like a LinkArray from the Draft WB. The App::LinkPython object gives you the oportunity to create your very own AppLink with all the logic you need, implemented in the execute function (in just one class). By the way, it even has an appLinkExecute function, that is evaluated for every item in the LinkArray and can be used to get an even better access to all your linked elements in the array.
Yes, in fact I suggested the idea of running original methods in Linked Feature Python in a Link, Realthunder implemented that, but Realthunder in a discussion indicates if there are a numbers of elements which would run the appLinkExecute, that would have impact on performance. It seems I had tried something along this approach and dropped into problem.

See [Link] of (Sketch)ObjectPython / Part FeaturePython lose its 'Proxy Methods' ?

On the other hand, I implement something to augment some Arch Objects to automatically place themselves in a position user input. The Link of these Arch Objects can thus, through appLinkExecute, also gain this feature. To my understanding, as I am using the stock App::Link with one or at most tens of ElementCount (imagine, there may be only be a few tens of same type of doors, maybe windows in a building, rarely up to thousands :) ), there seems minor, bearable impact on performance.

See - [ Comments Request /PR ] Intuitive Automatic Windows/Doors + Equipment Placement


So what is left to my knowledge are stock App::Link and App::LinkPython, it seems the stock App:Link can handle up to thousand of ElementCount with different placement (not using appLinkExecute) without significant performance impact (saved for the display issue).

So it is only my intuition to use stock App:Link with a numbers of my random experience - limited python knowledge, not trained as programmer, above background that the 'augmented feature' are implemented in the 'Linked Object' (Arch Objects) ( trying not to create more new objects), not sure about the performance etc. etc.

I have not really tested the performance of LinkArray and maybe I can try later. Thanks again for sharing your knowledge and experience, it is a big time saver! - With very limited programming knowledge, I usually learn through trial and error which take me very long time to make way through. :D
paullee
Veteran
Posts: 5098
Joined: Wed May 04, 2016 3:58 pm

Re: Generative Design with FreeCAD

Post by paullee »

j8sr0230 wrote: Sun May 29, 2022 9:34 am Before I forget, thanks for creating the cross-post. Are all my replies now automatically mirrored in this cross post, or does this just serve as a reference to this thread?
You are welcome. No, sorry that discussion here are not mirrored in the Arch/BIM forum :)

If I have Moved it, I could have left a 'Shadow' post here - or should these two threads be merged ? @kunda any suggestion what is best to do? Thanks.
User avatar
saso
Veteran
Posts: 1920
Joined: Fri May 16, 2014 1:14 pm
Contact:

Re: Generative Design with FreeCAD

Post by saso »

This is all very nice and if one searches the forum history they will find that I have for a long time support the idea for a node based UX/UI in FreeCAD, at the beginning more from the point of view from tools like GenerativeComponents, Grasshopper and Dynamo and later in microelly2's efforts with PyFlow...

BUT! Implementing an node based system inside FreeCAD by directly taking the example from the above mentioned tools or Blender is IMO wrong!

hammax posted a link to my post on polygonal (and polyhedral) mesh geometry, I did this post because I often saw a confusion and misunderstanding from users on different types of geometries (solids, curves, surfaces, meshes,...) and in this case the issue is somewhat similar, but it is not about the type of the geometry but about the modeling method / technique. At the bottom of the post about the meshes is a link to a different post about BRep (Boundary Representation) modeling, unfortunately I have never find the time to write down all this, but I had already then thought about this issue and from that list the two important topics about this are "parametric (associative) vs explicit modeling" and "node systems in explicit vs associative models".

The important thing to understand is that Rhino, Blender, Revit,... are in general (or by default) mostly explicit modelers, that means that there is mostly no direct association between the different elements in the model. Also the "generative" workflow of a node based system in this tools works mostly in an "extrinsic" way, meaning it creates the geometry and is able to modify it as long as the "generative procedure is active", but it is actually separate to the generated geometry and can, at least in most cases, in the end be even removed from the model while the generated geometry is kept.

This is NOT the case in an fully parametric workflow (also called feature based history or associative modeling) as is used in tools like Catia, NX, Creo, SolidWorks, Inventor, Fusion and of course FreeCAD (at least in general it should be :) ).

The first important thing to understand here is that "features" in the history tree and the "history tree" itself are basically already by their functionality and logic working as an node based system, features are the nodes and the associations between them is the linkage between the nodes. So, looking at the tree and understanding the logic of the model from the tree, is for the user in generally very much the same as looking at an node graph, just a bit different implementation of the UI... "sort of" :)

And the second important thing to understand is that in this case (with fully parametric modelers) the "associative" property between the features is "intrinsic" to the geometry, meaning you cannot just remove it, removing it removes also the geometry from the model, the features and the geometry (or we could also say the tree and the model) are basically the same thing, represented in two different ways. As the opposite to explicit modeling methods/tools where the nodes and the geometry are in general two separate things, that can work together sort of "on demand".

So, in FreeCAD (and other fully parametric modelers) not just the existing history tree, also for example the "dependency graph" already represent the associative structure of the model (as sort of a type of a node graph) and as explained above, they are intrinsic to the model.

This does however not mean that a node based system inside FreeCAD would be useless or even impossible to add, but that an implementation of it inside FreeCAD would have to take this existing properties of an parametric modeler in to consideration if one would want to implement it in a proper way that would allow it to be useful in general inside FreeCAD and not just for some specific cases.

To note, FreeCAD does also allow for the geometry of a model to be created in an explicit way, but implementing the node based system to work only in this way in FreeCAD would IMO limit its usefulness, be confusing to users (since it would work for some things and for others not) and in some cases maybe even represent incompatibility between the different geometries created inside FreeCAD (as we already have the case between the Part and Part Design WB).

Two examples of a node based system implemented in a fully parametric modelers, that I know of, are Siemens NX Algorithmic Modeling (https://www.youtube.com/watch?v=1OHZyY9Bh1A) and Catia/3DEXPERIENCE xGenerative Design (https://www.youtube.com/watch?v=ZtpalVl_-u0), by themselves they can look and feel very much the same as any other node system but the important thing is how they work with the rest of the model and this should be considered also for FreeCAD.
Last edited by saso on Mon May 30, 2022 7:04 pm, edited 1 time in total.
User avatar
j8sr0230
Posts: 140
Joined: Thu Apr 07, 2022 8:59 am
Location: Chemnitz
Contact:

Re: Generative Design with FreeCAD

Post by j8sr0230 »

paullee wrote: Sat May 28, 2022 1:26 am Trying to understand how it works, can you elaborate slightly on the usage of the attributes ?

And reading and hopefully could understand the codes :)
Ok, here is part 2: The documentation of the DualMesh class from my GenerativeDesign.py module. The DualMesh object converts a triangular mesh from Mesh Workbench into it’s dual, i.e. faces are turned into vertices and vertices are turned into faces as can be seen in the following figure https://dl.acm.org/doi/10.1145/2010324.1964998.
DualMesh (https://dl.acm.org/doi/10.1145/2010324.1964998)
DualMesh (https://dl.acm.org/doi/10.1145/2010324.1964998)
DualMesh.png (45.83 KiB) Viewed 1812 times
The starting point is a mesh of the Mesh Workbench that must be passed to the constructor of the class as a reference (mesh_link).

Code: Select all

def __init__(self, obj, mesh_link):
Furthermore, all properties are initiated in the constructor. MeshLink saves the link to the triangle mesh. DualVertices contains all nodes of the new dual mesh. These are calculated directly in the constructor and are determined from the centres of the triangle inner circles (compare figure above). Finally, there are the properties Scale, Thickness and BSpline to define the solid elements to be created.

Code: Select all

obj.addProperty("App::PropertyLinkSub", "MeshLink", "DualMesh", "Link to parent mesh", 1).MeshLink = mesh_link
obj.addProperty("App::PropertyVectorList", "DualVertices", "DualMesh", "Dual vertices of the mesh (centres of the inner circles)", 1)
obj.DualVertices = [tri.InCircle[0] for tri in obj.MeshLink[0].Mesh.Facets] # Faces are turned into vertices
obj.addProperty("App::PropertyFloat", "Scale", "DualMesh", "Scale factor of dual faces", 0).Scale = 1
obj.addProperty("App::PropertyFloat", "Thickness", "DualMesh", "Thickness of extrusion", 0).Thickness = 1
obj.addProperty("App::PropertyBool", "BSpline", "DualMesh", "Bspline (true) or default polygon (false)", 0).BSpline = True
To generate a single Voronoi element, I need the information which triangles share an individual mesh point. This is done in the following lines of code, still in the constructor of the class. Python's List Comprehension technology makes such a task quite easy.

Code: Select all

# Generate list of all faces linked to the same mesh vertex
self.linked_faces = []
for point in obj.MeshLink[0].Mesh.Points:
	faces = [idx for idx, face in enumerate(obj.MeshLink[0].Mesh.Topology[1]) \
		if (np.array(face) == point.Index).any()]
	self.linked_faces.append(faces)
Now that I know which faces (or face centres) belong to each new Voronoi element, I need to sort them in geometric order to generate a closed polygon or BSpline from them. This sorting can either be done via the distance of the face centres to each other, or one uses the topological information about the respective neighbouring triangular faces, which the mesh object already brings along. I decided on the quicker second option by arranging the faces according to their neighbours in a geometric sequence.

Code: Select all

# Sort list linked_faces by neighbours
for i in range(len(self.linked_faces)):
	faces = self.linked_faces[i]
	sorted_faces = [faces[0]]
	j = 1
	while j < len(faces):
		last_added = obj.MeshLink[0].Mesh.Facets[sorted_faces[-1]]
		for n in last_added.NeighbourIndices:
			if n not in sorted_faces and n in faces:
				sorted_faces.append(n)
				break
		j = j + 1
	self.linked_faces[i] = sorted_faces
In principle, everything is already solved in the constructor. The execute function now only contains basic topological scripting. At this point, the linked_faces object contains a list of geometrically sorted triangle indices and the DualVertices property contains the associated coordinates. According to the property BSpline (True or False) I create polygons or bsplines, which are then extruded to surfaces and solids. solid.Tag, solid.Hasher respect Realthunder's solution to the topological naming problem. If you want to use this class with a normal FreeCAD 0.20 version, these lines must be commented out. Otherwise you will get an error message.

Code: Select all

def execute(self, obj):
		# Generate shapes
		shape_list = []				
		for i, faces in enumerate(self.linked_faces): 
			if len(faces)>3: # Al least three vectors
				vectors = [obj.DualVertices[idx] for idx in faces]
				if obj.BSpline:
					wire = Part.BSplineCurve(vectors, None, None, True, 3, None, False)
					face = Part.makeFilledFace(wire)
					scaled_face = face.scale(obj.Scale, face.CenterOfMass)
					solid = scaled_face.extrude(obj.MeshLink[0].Mesh.Points[i].Normal * obj.Thickness)
					solid.Tag = i # Topological naming approach by realthunder
					solid.Hasher = App.StringHasher() # Topological naming approach by realthunder
					solid.Hasher.Threshold=10 # Topological naming approach by realthunder
					shape_list.append(solid)	
				else:
					vectors.append(vectors[0]) # Close chain by first face for closed polygon
					wire = Part.makePolygon(vectors)
					scaled_wire = wire.scale(obj.Scale, wire.CenterOfMass)
					face = Part.makeFilledFace(scaled_wire.Edges)
					solid = face.extrude(obj.MeshLink[0].Mesh.Points[i].Normal * obj.Thickness)
					solid.Tag = i # Topological naming approach by realthunder
					solid.Hasher = App.StringHasher() # Topological naming approach by realthunder
					solid.Hasher.Threshold=10 # Topological naming approach by realthunder
					shape_list.append(solid)				

		compound = Part.Compound(shape_list)
		obj.Shape = compound
Codelink on GitHub: https://github.com/j8sr0230/codelink
Codelink on PiPy: https://pypi.org/project/codelink/
FreeCAD Nodes Workbench on GitHub: https://github.com/j8sr0230/Nodes
User avatar
j8sr0230
Posts: 140
Joined: Thu Apr 07, 2022 8:59 am
Location: Chemnitz
Contact:

Re: Generative Design with FreeCAD

Post by j8sr0230 »

M4x wrote: Sun May 29, 2022 4:05 am Could this be used to generate a Voronoi pattern?
Yes, a Voronoi diagram can be created from the Delaunay triangulation (Mesh WB) with the DualMesh class (see illustration and FreeCAD file). Note: created with Raelthunder's Linkstage3 version of FreeCAD. (see illustration and FreeCAD file).
Planar Voronoi from tri mesh
Planar Voronoi from tri mesh
Planar Voronoi.png (906.32 KiB) Viewed 1790 times
Attachments
2022-05-30_Planar_Voronoi.FCStd
Linkstage3 file
(171.9 KiB) Downloaded 49 times
Codelink on GitHub: https://github.com/j8sr0230/codelink
Codelink on PiPy: https://pypi.org/project/codelink/
FreeCAD Nodes Workbench on GitHub: https://github.com/j8sr0230/Nodes
User avatar
M4x
Veteran
Posts: 1474
Joined: Sat Mar 11, 2017 9:23 am
Location: Germany

Re: Generative Design with FreeCAD

Post by M4x »

@j8sr0230 Wow, nice - thanks a lot! I've just finished a project using such a pattern. I'll test your approach on it nevertheless. I'll publish the project in the forum as soon as the documentation is ready (should take too long in this case).
User avatar
j8sr0230
Posts: 140
Joined: Thu Apr 07, 2022 8:59 am
Location: Chemnitz
Contact:

Re: Generative Design with FreeCAD

Post by j8sr0230 »

saso wrote: Sun May 29, 2022 12:49 pm The first important thing to understand here is that "features" in the history tree and the "history tree" itself are basically already by their functionality and logic working as an node based system, features are the nodes and the associations between them is the linkage between the nodes. So, looking at the tree and understanding the logic of the model from the tree, is for the user in generally very much the same as looking at an node graph, just a bit different implementation of the UI... "sort of" :)
[...]
Thank you for sharing your knowledge with us. I see it the same way you do, that the "features" in the history tree already represent a kind of node-based system. I also don't think that you should replace the history tree with a node-based system. I rather think that the static history tree system can be completed by a node-based modelling approach. Modern manufacturing processes are increasingly capable of producing organic/bionic structures, so a CAD system should be able to do this without changing the modelling paradigm from parametric to explicit (notwithstanding that there are good reasons for both paradigms, and a CAD system should be able to handle both if necessary).

In my opinion, a possible implementation could look like the following: Using a + button, the user is able to add an empty node tree to each FreeCAD document object. This node tree lives exclusively in the associated document object (like the traditional execute function in scripted objects) and can be used to manipulate the current shape using the OCCT functions provided by the Part module.

Of course, as with any node-based system, all this could be scripted and done in FreeCAD, for example, in the already existing execute function of App::FeaturePython objects. This poses the crucial question to all of us, whether such a system is desired by the community, or whether a motivated developer is just wasting his precious time :)
Codelink on GitHub: https://github.com/j8sr0230/codelink
Codelink on PiPy: https://pypi.org/project/codelink/
FreeCAD Nodes Workbench on GitHub: https://github.com/j8sr0230/Nodes
User avatar
hammax
Veteran
Posts: 1985
Joined: Thu Jan 19, 2017 5:03 pm
Location: Ammersee DE

Re: Generative Design with FreeCAD

Post by hammax »

... before I got knowledge of the above cited lecture
https://dl.acm.org/doi/10.1145/2010324.1964998
I only had a visual impression of Voronoi => what was it good for in an engineer's job?
Yesterday I tried to verify the Voronoi procedure in a simple sketch.
Some work without any program help, and I hope I understood the core task and problem.
(Chrystal growth in heat tempered soft solder connections, some 45 years ago - without computer help - was my task).
At the moment I try to find out, which one of the FC mesh generators would be the best choice for "Generative Design".

Beg_Voronoi.PNG
Beg_Voronoi.PNG (16.36 KiB) Viewed 1701 times
Attachments
Beginners_Voronoi.FCStd
FC.20
(16.06 KiB) Downloaded 37 times
paullee
Veteran
Posts: 5098
Joined: Wed May 04, 2016 3:58 pm

Re: Generative Design with FreeCAD

Post by paullee »

j8sr0230 wrote: Mon May 30, 2022 8:31 am

Code: Select all

def __init__(self, obj, mesh_link):
Furthermore, all properties are initiated in the constructor. MeshLink saves the link to the triangle mesh. DualVertices contains all nodes of the new dual mesh. These are calculated directly in the constructor and are determined from the centres of the triangle inner circles (compare figure above). Finally, there are the properties Scale, Thickness and BSpline to define the solid elements to be created.

Code: Select all

...
obj.DualVertices = [tri.InCircle[0] for tri in obj.MeshLink[0].Mesh.Facets] # Faces are turned into vertices
...
Thanks again taking time to explain the codes :D

Curious why calculation is done in the __init__ , so it is not expected user to change the MeshLink after its creation right ?

Code: Select all

		if (np.array(face) == point.Index).any()]
Trying to understand how this python line works but can't find answer yet on the web, any pointer ?

Code: Select all


...[b]solid.Tag, solid.Hasher respect Realthunder's solution to the topological naming problem. If you want to use this class with a normal FreeCAD 0.20 version, these lines must be commented out.[/b] Otherwise you will get an error message.

[code]
					solid.Tag = i # Topological naming approach by realthunder
					solid.Hasher = App.StringHasher() # Topological naming approach by realthunder
					solid.Hasher.Threshold=10 # Topological naming approach by realthunder
These lines looks interesting, any pointer to explain how does it works in Realthunder's branch. I learned from abdullah there is a Tag in Part.geometry e.g. the geometry in Sketcher, which is 'semi-persistent', curious any relevance here ?

Thanks again ! Too many questions :D
User avatar
j8sr0230
Posts: 140
Joined: Thu Apr 07, 2022 8:59 am
Location: Chemnitz
Contact:

Re: Generative Design with FreeCAD

Post by j8sr0230 »

paullee wrote: Tue May 31, 2022 5:18 pm Curious why calculation is done in the __init__ , so it is not expected user to change the MeshLink after its creation right ?
I'm sorry it took me so long to reply, but we were on holiday. Yes, at least that's how I implemented it at the time. You can also see this in the property status, which I initiated with Immutable = 1 (see the following code).

Code: Select all

obj.addProperty("App::PropertyLinkSub", "MeshLink", "DualMesh", "Link to parent mesh", 1).MeshLink = mesh_link
The calculation of the dual mesh and the creation of solids is computationally expensive. For this reason, I had not intended to exchange the mesh dynamically. However, it is possible by adapting the class accordingly.
Codelink on GitHub: https://github.com/j8sr0230/codelink
Codelink on PiPy: https://pypi.org/project/codelink/
FreeCAD Nodes Workbench on GitHub: https://github.com/j8sr0230/Nodes
Post Reply