Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancements for Assemblies #522

Open
bernhard-42 opened this issue Nov 26, 2020 · 13 comments
Open

Enhancements for Assemblies #522

bernhard-42 opened this issue Nov 26, 2020 · 13 comments
Labels
assembly enhancement New feature or request

Comments

@bernhard-42
Copy link
Contributor

As discussed in bernhard-42/jupyter-cadquery#23 I had added some enhancements to Assemblies which might be helpful for cadquery:

1 Enhancements

1.1 Chained assembly selectors "name1>name2>name3"

Problem: Using the same assemblies several times in an assembly

Simplified example full code:

    # Assembly 1: The leg
    leg = Assembly(upper_leg, name="upper").add(lower_leg, name="lower")

    # Assembly 2: The six legged Hexapod 
    hexapod = (Assembly(base, name="bottom"))
    for name in ['right_back', 'right_middle', 'right_front', 'left_back', 'left_middle', 'left_front']:
        hexapod.add(leg, name=name)

Now hexapod.objects only has one lower element:

{'bottom': <cadquery.assembly.Assembly at 0x7f65574286d0>,
 'right_back': <cadquery.assembly.Assembly at 0x7f65572e3220>,
 'upper': <cadquery.assembly.Assembly at 0x7f65573eb700>,
 'lower': <cadquery.assembly.Assembly at 0x7f6557450400>,
 'right_middle': <cadquery.assembly.Assembly at 0x7f65573d8e80>,
 'right_front': <cadquery.assembly.Assembly at 0x7f6557412550>,
 'left_back': <cadquery.assembly.Assembly at 0x7f655741e550>,
 'left_middle': <cadquery.assembly.Assembly at 0x7f65573ebe50>,
 'left_front': <cadquery.assembly.Assembly at 0x7f65573eb700>}

If you need to select the lower leg of the left back leg (e.g. to assemble yet another object), it doesn't seem to be obvious.

Solution approach: I enhanced the assembly selector to support the chain bottom>right_back>lower or, because it's also unique, right_back>lower. In MAssembly this is done via this function

1.2 Chaining object selectors

Problem: Many times one selects components via a chain, e.g. cad_obj.faces(">Z").edges(">X"). I did not see a way to do this via "obj_name?tag1@faces@>Z" query

Solution Approach: Besides string queries also allow tuple queries like ("obj_name?tag", "faces@>Z") and allow more than one object selector, e.g.: ("obj_name?tag", "faces@>Z", "edges@>Y").
Note: "obj_name?tag1@faces@>Z" and ("obj_name?tag", "faces@>Z") lead to the same result.

Of course, one could also use e.g. obj_name?tag1@faces@>Z@@edges@>X, but for me this seems to be harder to read and I anyhow needed the tuple selector for problem 3

1.3 Selecting one of several holes on a face

Problem: If you have e.g. several holes on a face and want to select a specific one, the only way that came to my mind was to use the NearestToPointSelector. But I wanted to use that with a simple query again.

Solution approach: Given the tuple query from above, I defined a new pattern: ("wires", (x,y)). This allowed me to do

for coord_2d in hole_coords:
    sel = ("obj_name?tag", "faces@>Z", ("wires", coord_2d))
    ...

In MAssembly 1.2 and 1.3 are implemented with these functions, however, currently without tag support.

2 Questions

  1. What is your take on my 3 problems. Do you see easier solutions already with the existing Assembly selectors?

  2. Would you be interested in a PR to solve these three problems (assuming there is no simple solution at the moment)

  3. The classes MAssembly and Mate are a little bit of aliens in my CAD viewer repo. Since the Massembly approach is based on cq.Assembly would you be interested in a PR to add the property mates and the methods mate(), assemble() to cq.Assemblies (both methods have 8 lines of code each) and of course the Mate class?
    This would add a static assembly approach (similar to cqparts and Assembly4 of FreeCad) parallel to your dynamic solver.

  4. If you would add the MAssembly functionality to cq.Assembly, would it be OK to support the origin property that holds the origin mate of an Assembly, even if this is only necessary for jupyter_cadquery? Otherwise I would need to monkey-patch CadQuery for origin support.
    The relocate() functions could be omitted, since they are only necessary for the animations system in jupyter-cadquery.

These are just a few ideas being developed while learning about assemblies and I would be happy to contribute some stuff to CadQuery if you are interested ...

@adam-urbanczyk adam-urbanczyk added assembly enhancement New feature or request labels Nov 27, 2020
@jmwright
Copy link
Member

@bernhard-42 Thanks for your work on this. There's a lot to like about your additions.

As mentioned in another thread I think, I would like to avoid the overloading of the > string selector operator as in right_back>lower. I could be convinced otherwise if the reasoning is compelling.

Would you be interested in a PR to solve these three problems

I would be supportive of it if you're willing to walk through the iterative process of submit->discuss->change->submit with @adam-urbanczyk and I. I think we all do better when we work through these bigger changes collaboratively.

The classes MAssembly and Mate are a little bit of aliens in my CAD viewer repo. Since the Massembly approach is based on cq.Assembly would you be interested in a PR to add the property mates and the methods mate(), assemble() to cq.Assemblies (both methods have 8 lines of code each) and of course the Mate class?

Mates like you've implemented are more in line with the way I think about 3D constraints. As long as we could pull these changes in without blocking future expansion of the existing assembly constraints system, I would be in support of it.

If you would add the MAssembly functionality to cq.Assembly, would it be OK to support the origin property that holds the origin mate of an Assembly, even if this is only necessary for jupyter_cadquery?

As long as there's not anything that breaks backwards-compatibility (and it sounds like there's not), I think that would be fine.

The relocate() functions could be omitted, since they are only necessary for the animations system in jupyter-cadquery.

Is rotate() only compatible with the threejs animation system? I've long thought that being able to export for kinematics (and animation) systems will be something that users will want eventually. I'm not sure if this is a move in that direction or not.

@bernhard-42
Copy link
Contributor Author

The reason why I described the problems first in my post is that I think they are generic. My solutions are an attempt to solve them based on my still limited understanding of Cadquery. So as long as I see a clear and simple way to create assemblies like my four examples I would be more than happy to adapt my approach.

Some more details:

  • I would have loved to use "name1/name2/name3" as paths to address components. Unfortunately, threejs parses "/" paths and then ignores them. So to be consistent in Cadquery and threejs queries, I selected ">". Any other char (or any other way of addressing cascaded assemblies) would be ok for me.

  • I started building the hexapod with Cadquery's solve approach, but I ran in some problems. So I started reading about assemblies. I like the mate approach of cqparts, but cqparts feels a bit too heavyweight for me (again, personal preference). And I liked the way Assembly4 addresses the topic. On the other side, I think the lightweight approach of Adam's assemblies is great. This is how the idea for MAssembly as a combination of all was born.
    The design idea was to keep the building of assemblies as is and to keep the mechanism to adapt the loc fields as the result of the assembly. The process in more abstract terms:
    "Cadquery assembly => assembly logic => Cadquery assembly with adapted loc fields"
    Adams way for the middle step (assembly logic) is dynamic, my way more static. But in both cases input and output are the same. This is how I thought to not break Cadquery. I think adding my two properties and few methods should not add issues to Cadquery.

  • I would see more need for discussions with my find methods. I don't think at the moment that the existing capabilities are sufficient. However, I would be really interested in proposals how to improve my attempts and maybe make them more Cadquery'ish.

  • The threejs animation system is based on the way threejs supports hierarchies. In fact, it is pretty much the same approach as Adam did. Nested groups, and instead of the loc field it has a translation and a quaternion per group. So a 1:1 translation was possible. The relocate stuff is just necessary for threejs to allow intuitive rotations and translations without further thinking: You want a 30 degree rotation around a mate, you request a 30 degree rotation without further transformations. Whether this is compatible with other systems, I don't know.

If you you would give me some guidance on how to address the three problems (and maybe some other points) I am happy to come up with a PR as the basis for further discussions.

@adam-urbanczyk
Copy link
Member

Thanks for opening the issue and starting the discussion. I'd like to focus for now on the issue 1.1, because the rest you can for now easily handle with tags or the alternative constrain overload. This does not mean it should not be eventually worked on.

The reason why I described the problems first in my post is that I think they are generic. My solutions are an attempt to solve them based on my still limited understanding of Cadquery. So as long as I see a clear and simple way to create assemblies like my four examples I would be more than happy to adapt my approach.

Some more details:

* I would have loved to use "name1/name2/name3" as paths to address components. Unfortunately, threejs parses "/" paths and then ignores them. So to be consistent in Cadquery and threejs queries, I selected ">". Any other char (or any other way of addressing cascaded assemblies) would be ok for me.

Currently deeply nested assemblies assume that all ids are unique, this is obviously not realistic, nor user friendly. I very much like the idea of constructing global IDs using some kind of a separator and adding them the objects dict. > will not play nicely with the current string selector syntax, hence the proposal of using / . What do you exactly mean with saying that threejs parses paths and ignores them? Why is actually threejs supposed to know about the ids used by CQ? If there are really good reasons to not use '/' then maybe . or : would be a reasonable choice?

@bernhard-42
Copy link
Contributor Author

I wanted to have the same selectors in CadQuery and Threejs. So it is not that Threejs would know about CadQuery, but from a usability perspective I'd liked to achieve this.
Now threejs parses selector paths as "/" or ":" .
However, then ignores it

I agree that "<" is not the best choice, so if we go for "/" or ":" I would need to have a different addressing for the animation part (I now provide the full path string "bottom>upper_leg>lower" as the id in threejs). Since threejs also parses for ".", all three are not optimal, so I searched for a different one (out of e.g. ,;>).

However, I understand if you'd like to use one of "/", ":" or ".". I would then need to think about how to best keep addressing in CadQuery and in threejs animation system in sync.

Of course, I would be interested in how you would use the existing features to achieve 1.2 and 1.3

@jmwright
Copy link
Member

@bernhard-42 Does Threejs allow you to escape the selector string? I.E. "bottom\/upper_leg\/lower"

Then maybe functions could be created to escape/unescape the paths to/from Threejs? Not ideal, but a potential solution. We could also use something unconventional like ~ or &, but they're not as intuitive as the other characters you both are discussing.

@bernhard-42
Copy link
Contributor Author

Finally, the delimiter character is not my biggest issue. I might go for offering both addressing methods for pythreejs groups in jupyter-cadquery: name1>name2>name3 (which is visible when you examine a pythreejs group) and for convenience in the Animation.add_number_track method also name1/name2/name3 (replacing "/" with ">" internally).
Not nice, but in my eyes acceptable - except you guys have a nice idea for a delimiter.

@adam-urbanczyk How would the construction of a unique delimited id work. If we take the example of the hexapod, for each leg that needs to be added, would we need a copy of the leg assembly with changed names? I'd like to ensure that same CadQuery objects (upper and lower legs) still have the same HashCode (to speed up the tessellation process by caching tessellation results and only tessellate uncached objects).

@bernhard-42
Copy link
Contributor Author

Re topic 1.3: I found the new Assembly tutorial in the docs and the approach to tag holes works very well, so let's ignore 1.3 as being resolved with standard CadQuery selectors

The only issue I had was that for

thickness = 2
height = 40
width = 65
diam = 4
tol = 0.05

l1, l2 = 50, 80
pts = [( 0,  0), ( 0, height/2), (l1, height/2 - 5), (l2, 0)]
upper_leg_hole = (l2 - 10, 0)

upper_leg = (cq.Workplane()
    .polyline(pts).mirrorX()
    .pushPoints([upper_leg_hole]).circle(diam/2 + tol).extrude(thickness)
    .edges("|Z and (not <X)").fillet(4)
)
upper_leg.faces(">Z").edges("%Circle")

image

I did not find a way to select the circle: upper_leg.faces(">Z").edges("%Circle").edges(">X[...]") lead to an list index out of range error for any value of .... So I had to use cq.NearestToPointSelector(upper_leg_hole).
Is this a bug or do I use it the wrong way?

@marcus7070
Copy link
Member

image

I did not find a way to select the circle: upper_leg.faces(">Z").edges("%Circle").edges(">X[...]") lead to an list index out of range error for any value of .... So I had to use cq.NearestToPointSelector(upper_leg_hole).
Is this a bug or do I use it the wrong way?

Is that outer circular edge and the hole concentric? The ">X" (DirectionMinMax) selector works off the Center property of Edges, so it will lump both of those together.

This exact problem made me do #504, try cq.selectors.RadiusNthSelector(0) to get the bolt hole.

@marcus7070
Copy link
Member

I think that IndexError might be a bug though, I would expect ">X[0]" to select some edges.

@bernhard-42
Copy link
Contributor Author

Let's leave the discussion about the circle selection out of this thread. I've opened another issue.
@marcus7070 I changed my example to make clear that the outer circular edge and the hole are not concentric. So it seems to be a different issue

@bernhard-42
Copy link
Contributor Author

I did some further analysis and found out the working with tags by leveraging the _query method has one drawback for me:
_query returns a Shape by using val(). As such the workplane info is lost. My approach to generate mates is to use the workplane of the selected face, wire, ... and take its normal as the z-axis (obj.workplane().plane.zDir) and same for x-axis. I want to ensure that if I create a mate on a plane it use the normal of the plane and not always (0,0,1) for z.

So to summarize the status of the three topics:

1.1: I agree, /, :, . would be the best choices to separate the path. I will find a way in jupyter_cadquery for the animation system

1.2 This can be done by using tags in a similar way as in the Assembly Tutorial (pending tests on chained objects as of 1.1)

1.3 This can be done by tags, however, it would be great to have _query either produce (name, Shape) or (name, Workplane)

@adam-urbanczyk
Copy link
Member

I did some further analysis and found out the working with tags by leveraging the _query method has one drawback for me:
_query returns a Shape by using val(). As such the workplane info is lost. My approach to generate mates is to use the workplane of the selected face, wire, ... and take its normal as the z-axis (obj.workplane().plane.zDir) and same for x-axis. I want to ensure that if I create a mate on a plane it use the normal of the plane and not always (0,0,1) for z.

you could use the normalAt(), normal() methods, or just implement _query to return the workplane. If you have tagged a circle are you actually interested in the workplane or in the circle? They could very well have different normals.

So to summarize the status of the three topics:

1.1: I agree, /, :, . would be the best choices to separate the path. I will find a way in jupyter_cadquery for the animation system

Thanks, I think this will be beneficial for the users.

1.2 This can be done by using tags in a similar way as in the Assembly Tutorial (pending tests on chained objects as of 1.1)

Note that tagging works with chained selectors too. Or actually: tagging is completely unrelated to selectors .

1.3 This can be done by tags, however, it would be great to have _query either produce (name, Shape) or (name, Workplane)

See above, we could provide another _query (_query_workplane ?), or you can add a different overload to constrain

@bernhard-42
Copy link
Contributor Author

Or actually: tagging is completely unrelated to selectors .

Indeed, tagging is very a powerful feature

If you have tagged a circle are you actually interested in the workplane or in the circle? They could very well have different normals.

The idea is to use holes to create mates. The origin of the mate should be the center of hole's circle on the face and x-dir and y-dir should be the xDir and yDir of the plane the circle lies in (basically for solids the z-dir of the mate should be directed to the outside):

image

obj = (
    cq.Workplane("front")
    .rect(30,30).workplane(offset=40.0).rect(7.5, 5).loft(combine=True)
    .faces(">Y").workplane().circle(2).cutThruAll()
)
c = obj.faces(">Y").edges(cq.NearestToPointSelector((0,10,16)))
a = MAssembly(obj, name="obj")
a.mate("c", "obj", mate=Mate(c))

Does this make sense?

Btw. the only way I found to select the circle was to use NearestToPointSelector. What I thought would be obvious, obj.faces(">Y").edges("%Circle"), did not work (while for a box parallel to x-, y- and z-axis it works).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
assembly enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants