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

How to access individual objects in a Compound object? #1715

Open
huskier opened this issue Nov 28, 2024 · 13 comments
Open

How to access individual objects in a Compound object? #1715

huskier opened this issue Nov 28, 2024 · 13 comments
Labels
question Further information is requested

Comments

@huskier
Copy link
Contributor

huskier commented Nov 28, 2024

We want to access individual letter in the text3D Compound, and we've found there is a function called siblings(self, shape: "Shape", kind: Shapes, level: int = 1). We wonder how to call the siblings function properly??//

import cadquery as cq
from cadquery.vis import show

text_string = "Hello, CadQuery!"
font = "Arial"
size = 10  # in millimeters
text3D = cq.Workplane("XZ").text(text_string, combine = True, font = font, fontsize = size, distance = 2).val()
@huskier huskier changed the title How to call Compound's siblings(...) function properly? How to access individual objects in a Compound object? Nov 29, 2024
@lorenzncode
Copy link
Member

access individual letter in the text3D Compound

You can access individual solid letters with iteration or unpacking.:

letter_o = [*text3D.val()][4]

or with selectors:

letter_nearest_point = text3D.val().solids(
    cq.selectors.NearestToPointSelector((0, 0, 0))
)

There are several ways to access the solids. It depends on your criteria for selection.
Here is an example sorting by volume with the Workplane sort method:

letters_by_vol = text3D.solids().sort(cq.Shape.Volume)
letter_largest_vol = letters_by_vol[-1]

@lorenzncode lorenzncode added the question Further information is requested label Nov 30, 2024
@huskier
Copy link
Contributor Author

huskier commented Dec 1, 2024

@lorenzncode Thank you for your help.

These methods are able to access the individual solid in the text3D Compound; however, we want to manipulate the individual solid which then affect the original text3D Compound. For example, we want to translate the letter "O" 100 in Z direction, and then we could get the changed text3D object with "O" letter translated 100 in Z direction. Can we achieve this effect with CadQuery?

import cadquery as cq
from cadquery.vis import show

text_string = "Hello, CadQuery!"
font = "Arial"
size = 10  # in millimeters
text3D = cq.Workplane("XZ").text(text_string, combine = True, font = font, fontsize = size, distance = 2).val()

letter_o = [*text3D][4]
letter_o.translate((0, 0, 100))
show(text3D)

letter_nearest_point = text3D.solids(
    cq.selectors.NearestToPointSelector((0, 0, 0))
)
letter_nearest_point.translate((0, 0, 100))
show(text3D)

@adam-urbanczyk
Copy link
Member

Currently a copy is made (I think in OCP, TBC). You'd need to recreate the compoud:

letters = list(text3D)
letters[0].move(z=10)
text3D = compound(letters)

@huskier
Copy link
Contributor Author

huskier commented Dec 1, 2024

letters = list(text3D)
letters[0].move(z=10)
text3D = compound(letters)

That's exactly what we want.

The code you provided could run as expected when the Compound is directly made by our own code; However, when the Compound object is imported from a STEP file (saved from our own code), then, we can not unpack the Compound object. The following is the MVP code. The STEP file "text_Hello_CadQuery_3D_asm.step" is saved from the commented code. We are working on an exploding function, and we need to access and manipulate both the Compound and the objects in the Compound.

import cadquery as cq
from cadquery.vis import show
from cadquery.occ_impl.shapes import compound

text_string = "Hello, CadQuery!"
font = "Arial"
size = 10  # in millimeters
text3D = cq.Workplane("XZ").text(text_string, combine = True, font = font, fontsize = size, distance = 2).val()

# letters = list(text3D)
# letters[0].move(z=10)
# text3D = compound(letters)
# show(text3D)

# text_Hello_3D_asm = (
#     cq.Assembly()
#     .add(letters[0], color = cq.Color(1, 1, 0), name = "H")
#     .add(letters[1], color = cq.Color(1, 1, 0), name = "e")
#     .add(letters[2], color = cq.Color(1, 1, 0), name = "l1")
#     .add(letters[3], color = cq.Color(1, 1, 0), name = "l2")
#     .add(letters[4], color = cq.Color(1, 1, 0), name = "o")
#     .add(letters[5], color = cq.Color(1, 1, 0), name = ",")
# )
# show(text_Hello_3D_asm)

# text_CadQuery_3D_asm = (
#     cq.Assembly()
#     .add(letters[6], color = cq.Color(1, 1, 0), name = "C")
#     .add(letters[7], color = cq.Color(1, 1, 0), name = "a")
#     .add(letters[8], color = cq.Color(1, 1, 0), name = "d")
#     .add(letters[9], color = cq.Color(1, 1, 0), name = "Q")
#     .add(letters[10], color = cq.Color(1, 1, 0), name = "u")
#     .add(letters[11], color = cq.Color(1, 1, 0), name = "e")
#     .add(letters[12], color = cq.Color(1, 1, 0), name = "r")
#     .add(letters[13], color = cq.Color(1, 1, 0), name = "y")
#     .add(letters[14], color = cq.Color(1, 1, 0), name = "!")
# )
# show(text_CadQuery_3D_asm)

# text_Hello_CadQuery_3D_asm = (
#     cq.Assembly()
#     .add(text_Hello_3D_asm, color = cq.Color(1, 1, 0), name = "Hello")
#     .add(text_CadQuery_3D_asm, color = cq.Color(1, 1, 0), name = "CadQuery")
# )
# show(text_Hello_CadQuery_3D_asm)
# text_Hello_CadQuery_3D_asm.save("text_Hello_CadQuery_3D_asm.step")

# text_Hello_CadQuery_3D = text_Hello_CadQuery_3D_asm.toCompound()
# text_Hello_CadQuery_3D_list = list(text_Hello_CadQuery_3D)
# print(len(text_Hello_CadQuery_3D_list))
# show(text_Hello_CadQuery_3D_list[0])
# show(text_Hello_CadQuery_3D_list[1])

# sub_text_Hello_CadQuery_3D_list = list(text_Hello_CadQuery_3D_list[0])
# print(len(sub_text_Hello_CadQuery_3D_list))
# show(sub_text_Hello_CadQuery_3D_list[0])
# show(sub_text_Hello_CadQuery_3D_list[1])

# The result of the above code is as expected! 

# The following code is not as expected. We expect that the text_Hello_CadQuery_3D_list  should be 2 at least, 
# but we get 1 instead.
text_Hello_CadQuery_3D = (
        cq.importers
        .importStep("text_Hello_CadQuery_3D_asm.step")
)
text_Hello_CadQuery_3D = text_Hello_CadQuery_3D.val()
print(type(text_Hello_CadQuery_3D))
show(text_Hello_CadQuery_3D)

show([*text_Hello_CadQuery_3D][0])
#show([*text_Hello_CadQuery_3D][1])

text_Hello_CadQuery_3D_list = list(text_Hello_CadQuery_3D)
print(len(text_Hello_CadQuery_3D_list))

@lorenzncode
Copy link
Member

Try flattening the compound, something like this:

def flatten(obj):
    if not isinstance(obj, Compound):
        yield obj
    else:
        for child in obj:
            yield from flatten(child)


print(len(list(flatten(text_Hello_CadQuery_3D))))  # 16

@huskier
Copy link
Contributor Author

huskier commented Dec 2, 2024

@lorenzncode Thank you for your help.

The flatten function has partially solved the problem. However, the flatten function can not keep the feature tree's hierarchical structure.

Right now, the flatten function could be my first stage solution.

Thank you for both of you, @adam-urbanczyk and @lorenzncode.

@adam-urbanczyk
Copy link
Member

I think you just need to unpack it once to get the original. See

@huskier
Copy link
Contributor Author

huskier commented Dec 3, 2024

I think you just need to unpack it once to get the original. See

@adam-urbanczyk Hi Adam, by "just need to unpack it once to get the original", what dou you mean? Do you mean we could simplify the followint code?

show([*text_Hello_CadQuery_3D][0])
#show([*text_Hello_CadQuery_3D][1])

Simplified into:

text_Hello_CadQuery_3D_list = list(text_Hello_CadQuery_3D)
show(text_Hello_CadQuery_3D_list [0])
show(text_Hello_CadQuery_3D_list [1])

@adam-urbanczyk
Copy link
Member

I mean that export wraps your text into another layer of compound, so you need to do something like orig, = *imported to get the original compound.

@huskier
Copy link
Contributor Author

huskier commented Dec 4, 2024

@adam-urbanczyk Thank you for your explanation. But I still do not get the point.

Let's see the following code, how should I get the original compound?

text_Hello_CadQuery_3D = (
        cq.importers
        .importStep("text_Hello_CadQuery_3D_asm.step")
)
# text_Hello_CadQuery_3D = (*text_Hello_CadQuery_3D).val()
# text_Hello_CadQuery_3D = *text_Hello_CadQuery_3D

@adam-urbanczyk
Copy link
Member

I think like this:

text_Hello_CadQuery_3D = list(text_Hello_CadQuery_3D.val())[0]

@huskier
Copy link
Contributor Author

huskier commented Dec 8, 2024

The code text_Hello_CadQuery_3D_list = list(list(text_Hello_CadQuery_3D.val())[0]) might seem awkward, but it effectively works without the assistance of the "flatten" function.

text_Hello_CadQuery_3D = (
        cq.importers
        .importStep("text_Hello_CadQuery_3D_asm.step")
)
text_Hello_CadQuery_3D_list = list(list(text_Hello_CadQuery_3D.val())[0])

show(text_Hello_CadQuery_3D_list[0])
show(text_Hello_CadQuery_3D_list[1])

After studying the Assembly.save() function and the cq.exporters.export() function, we found that
the Assembly.save() function adds an extra wrap (the red rectangle) in the STEP file, as shown in the following image. Could we get rid of the extra wrap in the Assembly.save() function?
text_Hello_CadQuery_3D_asm.save("text_Hello_CadQuery_3D_asm.step")
image

The cq.exporters.export() function performs well in maintaining the feature tree structure level. However, it fails to preserve user-defined names within the tree structure.

text_Hello_CadQuery_3D = text_Hello_CadQuery_3D_asm.toCompound()
cq.exporters.export(text_Hello_CadQuery_3D, "text_Hello_CadQuery_3D.step", "STEP")

image

@lorenzncode
Copy link
Member

Could we get rid of the extra wrap in the Assembly.save() function?

It's used to apply the top level location.:

# update the top level location

Another solution - to apply the top location to child subassemblies was already tried and rejected.

Perhaps the extra level can be removed when the top level location is not changed from the default. It would require more special handling.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants