CadQuery / cadquery

A python parametric CAD scripting framework based on OCCT
https://cadquery.readthedocs.io
Other
3.2k stars 290 forks source link

How to get the absolute location of a tag? #1635

Open lenianiva opened 3 months ago

lenianiva commented 3 months ago

Consider this example:

import cadquery as cq

def makeBoxWithTag():
    result = (
        cq.Workplane('XY')
        .box(1, 1, 1, centered=False)
    )
    result.vertices(">X and >Y and >Z").tag("v")
    return result
assembly = (
    cq.Assembly()
    .add(cq.Solid.makeBox(1, 1, 1), name="b1")
    .constrain("b1", "Fixed")
    .add(makeBoxWithTag(), name="b2", loc=cq.Location((0,0,1)))
    .constrain("b2", "Fixed")
    .add(cq.Solid.makeBox(1, 1, 1), name="b3", loc=cq.Location((0,0,2)))
    .constrain("b3", "Fixed")
    .add(cq.Solid.makeSphere(0.2, angleDegrees1=-90), name="m")
    .constrain("m", "b2@faces@>Y", "Point")
    .solve()
)

def print_loc(self: cq.Assembly, tag: str) -> cq.Location:
    name, shape = self._query(tag)
    loc_shape = shape.location()
    loc_centre = cq.Location(shape.Center())
    loc_parent, top = self._subloc(name)
    print(f"[{tag}] {name}: {shape}")
    print(f"    Shape: {loc_shape.toTuple()} ({type(loc_shape)})")
    print(f"    Centre: {loc_centre.toTuple()} ({type(loc_centre)})")
    print(f"    Parent: {loc_parent.toTuple()} ({type(loc_parent)})")
    _, top_shape = self._query(top)
    loc_top_shape = top_shape.location()
    print(f"    Top: {loc_top_shape.toTuple()}")
    print(f"    Top.loc: {top_shape.location().toTuple()}")
    loc_self = self.loc
    print(f"    Self: {loc_self.toTuple()}")
    loc = loc_parent * loc_centre
    print(f"    Total: {loc.toTuple()}")
print_loc(assembly, "b2@faces@>Y")
print_loc(assembly, "b2?v")

result = assembly

The location of the middle face has z = 1.5, but none of the functions I've tried in print_loc outputs a location of z = 1.5. How can I get its location?

absloc

lorenzncode commented 3 months ago

One method to access the locations might be with the assembly iterator:

import cadquery as cq

def makeBoxWithTag():
    result = cq.Workplane("XY").box(1, 1, 1, centered=False)
    result.vertices(">X and >Y and >Z").tag("v")
    return result

assembly = (
    cq.Assembly(name="myassy", loc=cq.Location((5, 0, 0)))  # added a name and top level location for testing
    .add(cq.Solid.makeBox(1, 1, 1), name="b1")
    .constrain("b1", "Fixed")
    .add(makeBoxWithTag(), name="b2", loc=cq.Location((0, 0, 1)))
    .constrain("b2", "Fixed")
    .add(cq.Solid.makeBox(1, 1, 1), name="b3", loc=cq.Location((0, 0, 2)))
    .constrain("b3", "Fixed")
    .add(cq.Solid.makeSphere(0.2, angleDegrees1=-90), name="m")
    .constrain("m", "b2@faces@>Y", "Point")
    .solve(0)
)

def toDict(assy):
    rv = {}
    for shape, name, loc, _ in assy:
        val = {}
        val["shape"] = shape
        val["loc"] = loc
        rv[name] = val
    return rv

b2loc = toDict(assembly)["myassy/b2"]["loc"]  # get the b2 location

b2 = assembly.objects["b2"].obj  # the Workplane box object b2
b2face = b2.faces(">Y")  # select the face
b2face = b2face.val().locate(b2loc)  # apply the location

print(b2face.Center())
lenianiva commented 2 months ago

One method to access the locations might be with the assembly iterator:

import cadquery as cq

def makeBoxWithTag():
    result = cq.Workplane("XY").box(1, 1, 1, centered=False)
    result.vertices(">X and >Y and >Z").tag("v")
    return result

assembly = (
    cq.Assembly(name="myassy", loc=cq.Location((5, 0, 0)))  # added a name and top level location for testing
    .add(cq.Solid.makeBox(1, 1, 1), name="b1")
    .constrain("b1", "Fixed")
    .add(makeBoxWithTag(), name="b2", loc=cq.Location((0, 0, 1)))
    .constrain("b2", "Fixed")
    .add(cq.Solid.makeBox(1, 1, 1), name="b3", loc=cq.Location((0, 0, 2)))
    .constrain("b3", "Fixed")
    .add(cq.Solid.makeSphere(0.2, angleDegrees1=-90), name="m")
    .constrain("m", "b2@faces@>Y", "Point")
    .solve(0)
)

def toDict(assy):
    rv = {}
    for shape, name, loc, _ in assy:
        val = {}
        val["shape"] = shape
        val["loc"] = loc
        rv[name] = val
    return rv

b2loc = toDict(assembly)["myassy/b2"]["loc"]  # get the b2 location

b2 = assembly.objects["b2"].obj  # the Workplane box object b2
b2face = b2.faces(">Y")  # select the face
b2face = b2face.val().locate(b2loc)  # apply the location

print(b2face.Center())

but this requires a traversal through the entire assembly tree. is there no way to get it directly?

adam-urbanczyk commented 2 months ago

Is that what you want: result.objects['m'].loc ?

lenianiva commented 1 month ago

Is that what you want: result.objects['m'].loc ?

you can't do that with a tag inside the object, e.g. b2?v

adam-urbanczyk commented 1 month ago

AFAICT there is no well defined location of the tagged object. You'd need to construct it yourself from its center and some prior knowledge or assumptions and then multiply with the loc and possibly locs of the parent assy objects.

Aren't we hitting the xy problem here? What are you trying to achieve in the end?

lenianiva commented 1 month ago

AFAICT there is no well defined location of the tagged object. You'd need to construct it yourself from its center and some prior knowledge or assumptions and then multiply with the loc and possibly locs of the parent assy objects.

Aren't we hitting the xy problem here? What are you trying to achieve in the end?

The use case is this: I have two tags on my (joint) assembly, child?mount and parent?mount, and a linear actuator is supposed to connect these. I want to ensure via a unit test that when the joint is closed, the two distance between the two mounts is some number, and when the joint is open, the distance between the two mounts is equal to the actuator's maximal length. I don't think this is an XY problem.

There's existing function in Assembly.solve that generates the location of a tag, so I think this is definitely doable.

adam-urbanczyk commented 1 month ago

I guess you want something like this then?

name = 'b2'
tag = 'v'

loc = result.objects['b2'].loc
tagged = result.objects['b2'].obj._getTagged('v').val()

result: Vector = tagged.moved(loc).Center()
lenianiva commented 1 week ago

I guess you want something like this then?

name = 'b2'
tag = 'v'

loc = result.objects['b2'].loc
tagged = result.objects['b2'].obj._getTagged('v').val()

result: Vector = tagged.moved(loc).Center()

can you do this with something like b2@faces@>Y?