CadQuery / cadquery

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

Multiple split with Faces produce inconsistent results #1461

Open chemiskyy opened 11 months ago

chemiskyy commented 11 months ago

We are trying to generate multiple cuts to 'raster' basic geometries The sample code below produces inconsistent results with missing parts of the split ellipsoid

import cadquery as cq
import numpy as np

a_x=0.15
a_y=0.31
a_z=0.4
grid=[4, 4, 4]

transform_mat = cq.Matrix(
    [
        [a_x, 0, 0, 0.],
        [0, a_y, 0, 0.],
        [0, 0, a_z, 0.],
    ]
)

sphere = cq.Solid.makeSphere(1.0, cq.Vector(0, 0, 0), angleDegrees1=-90)
ellipsoid = sphere.transformGeometry(transform_mat)

solidList = []

for solid in ellipsoid.Solids():
    wk_plane = cq.Workplane().add(solid)
    xgrid = np.linspace(-0.5, 0.5, num=grid[0], endpoint=False)[1:]
    ygrid = np.linspace(-0.5, 0.5, num=grid[0], endpoint=False)[1:]
    zgrid = np.linspace(-0.5, 0.5, num=grid[0], endpoint=False)[1:]
    print(ygrid)
    for i in xgrid:
        Plane_x = cq.Face.makePlane(None, None, basePnt=(i, 0, 0), dir=(1, 0, 0))
        wk_plane = wk_plane.split(cq.Workplane().add(Plane_x))
        print('x:', len(wk_plane.val().Solids()))         
    for j in ygrid:
        Plane_y = cq.Face.makePlane(None, None, basePnt=(0, j, 0), dir=(0, 1, 0))
        wk_plane = wk_plane.solids().split(cq.Workplane().add(Plane_y), keepBottom=True, keepTop=True)
        print('y:', len(wk_plane.val().Solids()))       
    for k in zgrid:
        Plane_z = cq.Face.makePlane(None, None, basePnt=(0, 0, k), dir=(0, 0, 1))
        wk_plane = wk_plane.split(cq.Workplane().add(Plane_z), keepBottom=True, keepTop=True)
        print('z:', len(wk_plane.val().Solids()))                

    for subsolid in wk_plane.val().Solids():
        solidList.append(subsolid)

compound = cq.Compound.makeCompound(solidList)
cq.exporters.export(compound, "compound.step")

image_480

It is however expected to keep all the split solids a grid of [5,5,5] provide expected results : image_480

kmarchais commented 11 months ago

The following code is the same example simplified that can run in CQ-editor. Splitting in only one dimension works fine but as soon as we split in two or three dimensions (for dim in [0, 1] for example), some parts are missing as shown in the screenshot below.

import cadquery as cq

sphere = cq.Workplane().sphere(1.0).val()
a_x = 0.15
a_y = 0.31
a_z = 0.4

transform_mat = cq.Matrix(
    [
        [a_x, 0, 0, 0.0],
        [0, a_y, 0, 0.0],
        [0, 0, a_z, 0.0],
    ]
)

ellipsoid = sphere.transformGeometry(transform_mat)
workplane = cq.Workplane().add(ellipsoid)
for dim in range(3):
    direction = cq.Vector([int(dim == i) for i in range(3)])
    for pos in [-0.25, 0.0, 0.25]:
        point = pos * direction
        plane = cq.Face.makePlane(basePnt=point, dir=direction)
        workplane = workplane.split(plane)

image

kmarchais commented 11 months ago

As found by @chemiskyy, using transformGeometry is creating the problem. Here is an example of a sphere with and without transformGeometry: image image

In our case, we have to use transformGeometry to create the ellipsoid shape. A workaround found by @chemiskyy is to generate the sphere with a random direction.

a_x = 0.15
a_y = 0.31
a_z = 0.4

transform_mat = cq.Matrix(
    [
        [a_x, 0, 0, 0.],
        [0, a_y, 0, 0.],
        [0, 0, a_z, 0.],
        [0, 0, 0, 1.],        
    ]
)

sphere = cq.Solid.makeSphere(
    0.25,
    cq.Vector(0, 0, 0),
    dir=cq.Vector(0.1, 0.1, 0.1),  # problem with the default direction  
    angleDegrees1=-90,
)
ellipsoid = sphere.transformGeometry(transform_mat)

Plane_x = cq.Face.makePlane(None, None, basePnt=(0., 0, 0), dir=(1, 0, 0))
Plane_y = cq.Face.makePlane(None, None, basePnt=(0., 0, 0), dir=(0, 1, 0))

ellipsoid = ellipsoid.split(Plane_x)
ellipsoid = ellipsoid.split(Plane_y)

workplane = cq.Workplane().add(ellipsoid)

So perhaps BRepAlgoAPI_Splitter is not behaving as expected when attempting to split in the same direction as the one used to create the sphere.

adam-urbanczyk commented 10 months ago

This works better, but not quite there:

import cadquery as cq

sphere = cq.Workplane().sphere(1.0).val()
a_x = 0.15
a_y = 0.31
a_z = 0.4

transform_mat = cq.Matrix(
    [
        [a_x, 0, 0, 0.0],
        [0, a_y, 0, 0.0],
        [0, 0, a_z, 0.0],
    ]
)

ellipsoid = sphere.transformGeometry(transform_mat)
workplane = cq.Workplane().add(ellipsoid)

splitters = []
for dim in range(3):
    direction = cq.Vector([int(dim == i) for i in range(3)])
    for pos in [-0.25, 0.0, 0.25]:
        point = pos * direction
        plane = cq.Face.makePlane(20,20,basePnt=point, dir=direction)
        splitters.append(plane)

res = workplane.val().split(*splitters)
show_object(res)