CadQuery / cadquery

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

cadquery.Solid.extrudeLinear multimethod not always dispatching correctly (master) #1303

Closed ze897c closed 1 year ago

ze897c commented 1 year ago

cadquery.Solid.extrudeLinear dispatches to

    @multimethod
    def extrudeLinear(
        cls,
        outerWire: Wire,
        innerWires: List[Wire],
        vecNormal: VectorLike,
        taper: Real = 0,
    ) -> "Solid":

instead of

    @classmethod
    @extrudeLinear.register
    def extrudeLinear(
        cls, face: Face, vecNormal: VectorLike, taper: Real = 0,
    ) -> "Solid":

...but not always.

To Reproduce

Not minimal, sorry, but this cause the behavior for me: stupid_extrude_wrap is my work-around, and fixes behavior for me.

Thanks folks.

import itertools as it
import typing as t
from pathlib import Path

import OCP as ocp
import cadquery as cq
import numpy as np
from OCP.BRepBuilderAPI import (BRepBuilderAPI_MakeSolid, BRepBuilderAPI_MakeWire,
    BRepBuilderAPI_MakeEdge,)
from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox
from OCP.TopoDS import TopoDS_Wire
from OCP.gp import gp_Pnt, gp_Vec
from jupyter_cadquery.viewer.client import show_object

def stupid_extrude_wrap(*args) -> cq.Solid:
    """OCP multimethod is totally borked"""
    for f in cq.Solid.extrudeLinear.pending:
        try:
            return f(cq.Solid, *args)
        except TypeError:
            continue
    raise RuntimeError('now what?')

class Dimensions(t.NamedTuple):
    w: float = 1
    l: float = 1
    h: float = 1
    e: float = .1
    o: np.ndarray = np.zeros(3)

    def scale(self, a: float) -> 'Dimensions':
        return Dimensions(
            w=a * self.w,
            l=a * self.l,
            h=a * self.h,
            e=a * self.e,
        )

    @property
    def box(self) -> cq.Solid:
        rex = cq.Solid(BRepPrimAPI_MakeBox(gp_Pnt(*self.o), self.w, self.l, self.h).Solid())
        rex.label = 'box'
        return rex

    @property
    def t(self):
        return self.e * np.linalg.norm((self.w, self.l, self.h))

    @property
    def punch_out_face_points(self) -> np.ndarray:
        t = self.t
        a = self.o + t * np.asarray([1, -1, 1])
        b = self.o + np.asarray([0, 0, self.h]) + t * np.asarray([1, -1, -1])
        c = self.o + np.asarray([self.w, 0, self.h]) + t * np.asarray([-1, -1, -1])
        return np.asarray([a, c, b, a])

    @property
    def punch_out_face(self) -> cq.Face:
        return np2face(self.punch_out_face_points)

    @property
    def punch_out_prism_vector(self) -> gp_Vec:
        v = self.punch_out_face_points
        return gp_Vec(
                gp_Pnt(*v[0]),
                gp_Pnt(*(v[0] + np.asarray([0, self.l + 2 * self.t, 0]))),
            )

    @property
    def punch_out(self) -> cq.Solid:
        """
        would have been better/easier to build a face with hole & extrude, but I wanted to
        know how to do Boolean ops too
        :return:
        """
        rex = stupid_extrude_wrap(
            self.punch_out_face,
            self.punch_out_prism_vector,
        )
        rex.label = 'punch-out'
        return rex

    @property
    def punched(self) -> cq.Shape:
        rex = self.box.cut(self.punch_out)
        rex.label = 'punched'
        return rex

    @property
    def assembly(self):
        rex = cq.Assembly()

        box = self.box
        punch_out = self.punch_out
        punched = self.punched

        rex.add(
            box,
            name=box.label,
            color=cq.Color('red'),
        )

        rex.add(
            punch_out,
            name=punch_out.label,
            color=cq.Color('blue'),
        )

        rex.add(
            punched,
            name=punched.label,
            color=cq.Color('green'),
        )

        return rex

def shell2solid(shell):
    ms = BRepBuilderAPI_MakeSolid()
    ms.Add(ocp.topods.Shell(shell))
    solid = ms.Solid()
    return solid

def np2face(v: np.ndarray) -> cq.Face:
    return cq.Face.makeNSidedSurface(
        edges=[
            # BRepBuilderAPI_MakeEdge(
            cq.Edge.makeLine(
                gp_Pnt(*a),
                gp_Pnt(*b),
            )
            for a, b in it.pairwise(v)
        ],
        constraints=[],
    )

def np2wire(v: np.ndarray) -> TopoDS_Wire:
    return BRepBuilderAPI_MakeWire(*[
            BRepBuilderAPI_MakeEdge(
                gp_Pnt(*a),
                gp_Pnt(*b),
            ).Edge()
            for a, b in it.pairwise(v)
        ]).Wire()

if __name__ == '__main__':

    ddir = Path('~/data/ika')
    ddir.mkdir(parents=True, exist_ok=True)
    # display, start_display, add_menu, add_function_to_menu = init_display()

    dim = Dimensions(e=.05)
    sid = dim.box
    feg = dim.punch_out_face
    pou = dim.punch_out
    elmo = dim.assembly

    # jcv = open_viewer('OCC')

    show_object(elmo)
    elmo.save(
        str(ddir / f'{elmo.name}.step')
    )

Backtrace

Environment

OS: OSX

Was CadQuery installed using Conda?: Output of conda list from your active Conda environment:

Yep: conda installed

conda list | grep cad
cad-viewer-widget         1.4.1                    pypi_0    pypi
cadquery                  master                   py3.10    cadquery
cadquery-massembly        1.0.0                    pypi_0    pypi
cq-editor                 master                   py3.10    cadquery
jupyter-cadquery          3.5.2                    pypi_0    pypi

Using: pythonn3.10 interpreter

lorenzncode commented 1 year ago

This example passes an instance of type OCP.gp.gp_Vec.

VectorLike instead expects cadquery.occ_impl.geom.Vector (or tuples).

See https://github.com/CadQuery/cadquery/blob/master/cadquery/occ_impl/geom.py#L28

You could wrap the OCP type with cq.Vector:


        rex = cq.Solid.extrudeLinear(
            self.punch_out_face,
            cq.Vector(self.punch_out_prism_vector),
        )
ze897c commented 1 year ago

This example passes an instance of type OCP.gp.gp_Vec.

VectorLike instead expects cadquery.occ_impl.geom.Vector (or tuples).

See https://github.com/CadQuery/cadquery/blob/master/cadquery/occ_impl/geom.py#L28

You could wrap the OCP type with cq.Vector:

        rex = cq.Solid.extrudeLinear(
            self.punch_out_face,
            cq.Vector(self.punch_out_prism_vector),
        )
ze897c commented 1 year ago

Aha, thanks @lorenzncode. I, clearly, didn't catch that.