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

Adding extrude/cutBlind untilNextSurf option #776

Closed
Jojain opened this issue May 24, 2021 · 10 comments
Closed

Adding extrude/cutBlind untilNextSurf option #776

Jojain opened this issue May 24, 2021 · 10 comments
Labels
OCC feature Requires coding at OCC implementation level

Comments

@Jojain
Copy link
Contributor

Jojain commented May 24, 2021

Hello,
I wanted to discuss about a possible new feature for extruding and cutting with prisms.
In Catia V5 and maybe in other CAD softwares there is a nice option when extruding or cutting material from a part which is the untilNextSurf. Below is an exemple of what the behavior would be :

import cadquery as cq

pts = [(0,0),
       (3,0),
       (3,5),
       (2,5),
       (2,1),
       (1,1),
       (1,5),
       (0,5)
       ]
s = (cq.Workplane("XY")
     .polyline(pts)
     .close()
     .extrude(2)
     .faces(">X")
     .workplane(centerOption = "CenterOfMass")
     .rect(1,1.6)
     .cutBlind(untilNextSurf = True)
     )

image

import cadquery as cq

pts = [(0,0),
       (3,0),
       (3,5),
       (2,5),
       (2,1),
       (1,1),
       (1,5),
       (0,5)
       ]
s = (cq.Workplane("XY")
     .polyline(pts)
     .close()
     .extrude(2)
     .faces(">>X[2]")
     .workplane(centerOption = "CenterOfMass")
     .rect(1,1.6)
     .extrude(untilNextSurf = True)
     )

image

It is quite trivial for planar faces but it would be expected to work also for non planar faces destinations. I guess for non planar faces we couldn't use the Solid.extrudeLinear so it would need to be done through lofting or another operation, so it would require a bit more changes.

@adam-urbanczyk adam-urbanczyk added the OCC feature Requires coding at OCC implementation level label May 24, 2021
@adam-urbanczyk
Copy link
Member

NB: it is already supported by OCCT (e.g. https://dev.opencascade.org/doc/refman/html/class_b_rep_feat___make_d_prism.html#a39fa6af774981a949eeca992bda75adf). It "just" needs to be properly exposed.

@Jojain
Copy link
Contributor Author

Jojain commented May 24, 2021

Should this be exposed at occ_impl level and in Workplane class or only in Workplane class ?

I notice aswell that the Solid.dprism method is only used in Workplane.cutThruAll but not for Workplane.extrude , so if it should be added at occ_impl level I don't know where/how to implement it.

For reference, my little test :

import cadquery as cq
from OCP.BRepFeat import BRepFeat_MakeDPrism
from OCP.TopoDS import TopoDS_Face 
from jupyter_cadquery.viewer.client import show

def extrude_until_next(solid, wire, until_face):
    face = cq.Face.makeFromWires(wire)
    maker = BRepFeat_MakeDPrism(solid.wrapped, face.wrapped, TopoDS_Face(), 0, 1, True)
    maker.Perform(until_face.wrapped)
    shape = maker.Shape()
    return shape 

box = cq.Workplane().box(10,10,10)
wire = box.faces(">X").workplane().rect(4,2).val()
end_face = cq.Workplane(origin=(30,0,0)).sphere(10).split(keepBottom=True).rotate((30,0,0),(30,1,0),90).faces("<X").val()
s = extrude_until_next(box.val(), wire, end_face )
s = cq.Shape.cast(s)
show(s, end_face, cad_width = 1500, height = 900, default_edgecolor = (0,0,0), axes=True)

image

@adam-urbanczyk
Copy link
Member

Should this be exposed at occ_impl level and in Workplane class or only in Workplane class ?

I notice aswell that the Solid.dprism method is only used in Workplane.cutThruAll but not for Workplane.extrude , so if it should be added at occ_impl level I don't know where/how to implement it.

What is your question actually? OCP is used in occ_impl which is (sometimes) used on the fluent level.

@Jojain
Copy link
Contributor Author

Jojain commented May 24, 2021

Ok sorry, my question is : should this be implemented in the fluent API directly from OCP or should there first be a intermediate layer in occ_impl which would then be used in the fluent API ?
(That's why I was making a reference to Solid.dprism that could be the intermediate layer)

From what I understand, the plans with cadquery is to have several layers of abstraction, OCP, occ_impl and the fluent API. And I assume that when exposing new features you usually want to add them first in occ_impl and then used that to implement it to the fluent API. Am I wrong ?

@jmwright
Copy link
Member

jmwright commented Jun 1, 2021

@Jojain I think it may be helpful for you to look at how extrude is implemented to see how this might be broken down across the abstraction layers in a similar way. The call chain is something like this:

Workplane.extrude -> Workplane._extrude -> occ_impl.shapes.Solid.extrudeLinear

@Jojain
Copy link
Contributor Author

Jojain commented Jun 2, 2021

I have been working on it and got it working but before going too deep in the implementation I would like to have a consensus of how it should be implemented.
So my questions are :

  • Can I return OCCT object in a function of occ_impl.py or do I need to wrap the underlying OCCT object in a new class (for exemple gp_Pnt ) ?
  • The BRepFeat_MakeDPrism extrude or cut material from an existing solid, which is different from BRepPrimAPI_MakePrism so this leads to this question :

Given the following calls for an extrusion from a Workplane :

  • Workplane.extrude -> _extrude -> extrudeLinear
  • Should I add the untilSurf option to extrudeLinear or should I create a new method extrudeUntilSurf to keep things more clean ?

@adam-urbanczyk
Copy link
Member

adam-urbanczyk commented Jun 2, 2021

So my questions are :

* Can I return OCCT object in a function of `occ_impl.py` or do I need to wrap the underlying OCCT object in a new class (for exemple `gp_Pnt` ) ?

You cannot return OCCT objects. Which new class do you want to wrap and why?

* The `BRepFeat_MakeDPrism` extrude or cut material from an existing solid, which is different from `BRepPrimAPI_MakePrism` so this leads to this question :

Given the following calls for an extrusion from a Workplane :

* `Workplane.extrude` -> `_extrude` -> `extrudeLinear `

* Should I add the `untilSurf` option to `extrudeLinear` or should I create a new method `extrudeUntilSurf` to keep things more clean ?

Probably a new method in the occ_impl layer would be better.

@Jojain
Copy link
Contributor Author

Jojain commented Jun 2, 2021

You cannot return OCCT objects. Which new class do you want to wrap and why?

It was more to know if I could do that, I don't think I will need to wrap a new class there is several workaround.

To share more of my process, I started to write this as a "face selector"

def _getNthFace(self, n: int, alongNormal: bool = True, tol: float = 1e-4):
    """
    Returns the Nth face from the current object on the stack on the specified direction
    To do so it computes the distance from the center of mass object to the intersection point 
    of a given face with the line defined by the object center of mass and the Workplane.plane.zDir

    :param n: Nth face to select
    :param alongNormal: If True search along plane normal else along vector opposite to plane normal
    :param tol: Line on face intersection tolerance
    """
    cog = gp_Pnt(*self.val().Center().toTuple())
    dir = gp_Dir(self.plane.zDir.wrapped)

    line = gce_MakeLin(cog, dir).Value() 
    tol = tol
    shape = self.findSolid().wrapped

    intersectMaker = BRepIntCurveSurface_Inter()
    intersectMaker.Init(shape, line,  tol)
    
    faces = []
    while intersectMaker.More():

        interPt = intersectMaker.Pnt()

        interDir = gce_MakeDir(cog, interPt)

        if interDir.IsDone():            
            interDir = interDir.Value()
        else:
            intersectMaker.Next()
            continue

        distance = cog.SquareDistance(interPt)
        if distance < tol: # intersection with the face where the wire is lying
            intersectMaker.Next()
            continue
        
        if not interDir.IsOpposite(dir, tol):
            faces.append((intersectMaker.Face(), distance))

        intersectMaker.Next()
        
    faces.sort(key= lambda x: x[1])
    return faces[n][0]  

Now I'm trying to break it down in several parts to be in accordance with the cadquery layers

I end up with this intermediary function :

def facesIntersectedByLine(point: VectorLike, axis: VectorLike, shape: "Shape", tol: float = 1e-4, direction: Optional[str] = None ):
    """
    Computes the intersections between the provided line and the faces of the provided shape
    """
    point = gp_Pnt(*point.toTuple()) if isinstance(point, Vector) else gp_Pnt(*point)
    axis = gp_Dir(Vector(axis)) if not isinstance(axis, Vector) else gp_Dir(axis)

    line = gce_MakeLin(point, axis).Value() 
    shape = shape.wrapped

    intersectMaker = BRepIntCurveSurface_Inter()
    intersectMaker.Init(shape, line,  tol)

    faces = []
    while intersectMaker.More():
        interPt = intersectMaker.Pnt()
        interDirMk = gce_MakeDir(point, interPt)

        distance = point.SquareDistance(interPt)

        # interDir is not done when `point` and `axis` have the same coord
        if interDirMk.IsDone():            
            interDir = interDirMk.Value()
        else:
            interDir = None

        if direction == "AlongAxis":
            if interDir is not None and not interDir.IsOpposite(axis, tol) and distance > tol: 
                faces.append((intersectMaker.Face(), distance))

        elif direction == "Opposite":
            if interDir is not None and interDir.IsOpposite(axis, tol) and distance > tol: 
                faces.append((intersectMaker.Face(), distance))

        elif direction is not None:
            raise ValueError("Unvalid direction specification.\nValid specification are 'AlongAxis' and 'Opposite'.")

        else:
            faces.append((intersectMaker.Face(), abs(distance)))
        intersectMaker.Next()

        faces = [face[0] for face in faces.sorted(key = lambda x: x[1])]
        
    return [TopoDS_Face.cast(face) for face in faces]

But I don't really find place for it, it is kindof a selector but at occ_impl level, maybe in geom.py ? Or I should rethink everything?

@adam-urbanczyk
Copy link
Member

I'd propose to add the method to the Mixin3D mixin class. Having played with MakeDPrism I'm not sure that the intersect by line is the optimal way forward. Let's discuss in your PR further.

@adam-urbanczyk
Copy link
Member

Resolved in #875

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
OCC feature Requires coding at OCC implementation level
Projects
None yet
Development

No branches or pull requests

3 participants