3
$\begingroup$

I am trying to cut out a section from a "spherical shell of yarn", without much success.

Using a python script, I created a series of coordinates on a spherical surface:

import bpy
import numpy as np
import random

npoints = 100
R0 = 1

a = 1.0-np.random.rand()*2.0
theta = np.random.rand()*2*np.pi
coords = []
for j in range(npoints):
    b = (np.random.rand()-0.5)/np.sqrt(npoints)*2
    if (a+b > 1.0 or a+b < -1.0):
            a = a-b
    else:
            a = a+b
    phi = np.arccos(a)
    theta += np.random.rand()*2*np.pi/npoints
    R = R0*(1.0+np.random.rand()/10.)
    coords.append((R*np.cos(theta)*np.sin(phi), R*np.sin(theta)*np.sin(phi), R*np.cos(phi)))
# create the Curve Datablock
    curveData = bpy.data.curves.new('myCurve', type='CURVE')
    curveData.dimensions = '3D'
    curveData.resolution_u = 2
    curveData.fill_mode = 'FULL'
    curveData.bevel_depth = 0.02
    #curveData.materials.append(mat1)

# map coords to spline
    polyline = curveData.splines.new('NURBS')
    polyline.points.add(len(coords))
    for i, coord in enumerate(coords):
        x,y,z = coord
        polyline.points[i].co = (x, y, z, 1)

# create Object
    curveOB = bpy.data.objects.new('myCurve', curveData)
    curveOB.rotation_euler = (np.pi-2.*np.random.rand()*np.pi,np.pi-2.*np.random.rand()*2.*np.pi,np.pi-np.random.rand()*np.pi)  # Note that you need to use radians rather than angles here

# attach to scene and validate context
    scn = bpy.context.scene
    scn.objects.link(curveOB)
    scn.objects.active = curveOB
    curveOB.select = False

This generates a shell of yarn, as showed in the image below. Shell of yarn created with the code above

I would like now to cut out a quarter of this spherical shell. To do that, I tought to add a cube to the scene and apply the boolean operator "difference" to all of the curves. Indeed, this strategy does not work as I cannot aplly a boolean operator to the bezier curves... I am now wondering how to proceed.

Could another option be to identify all coordinates of the bezier which are found in a certain region of the scene and eliminate them?

Addition after the comment, stating if I cannot modify the script. I can, but the result is not as i wanted. What I can do, is to rotate the chain with the script, and eliminate all the coordinates which are located in a certain volume. But then, when I create the curve, all the coordinates will be connected, and therefore there will be still chain segments (straight) in the area I wanto to free.

Once the coordinates of the chain were created, I rotated it with

def randomrot(coords):
x = 2.*np.random.rand()*np.pi -np.pi #random angle between 0 and 2pi
y = 2.*np.random.rand()*np.pi  #random angle between 0 and 2pi
z = 2.*np.random.rand()*np.pi  #random angle between 0 and 2pi

Rx = [[1., 0., 0.], [0., np.cos(x), -np.sin(x)], [0., np.sin(x), np.cos(x)]]
Ry = [[np.cos(y), 0., np.sin(y)], [0., 1.0, 0.0], [-np.sin(y), 0.0, np.cos(y)]]
Rz = [[np.cos(z), -np.sin(z), 0.], [np.sin(z), np.cos(z), 0.0], [0., 0., 1.]]
return np.matmul(np.matmul(np.matmul(coords,Rx),Ry),Rz)

Then I can cut out all the points from the coordinate list which are located in a certain volume with:

        for i in range(len(rotcoords)):  #cuts the top quarter corner of the polymer sghell
        if rotcoords[i,0] < 0.0 and  rotcoords[i,1] < 0.0 and rotcoords[i,2] > 0.5:
            None
        else:
            newcoords.append(new[i,:])

The output then looks so:

enter image description here

Many greets, Leo

$\endgroup$
  • $\begingroup$ can you instead of trying to cut curves, edit script? $\endgroup$ – Crantisz Aug 31 '18 at 12:27
  • $\begingroup$ I completed the question with your comment... It does not completely work out. $\endgroup$ – leonardo2887 Aug 31 '18 at 13:50
  • 1
    $\begingroup$ Please upload a .blend $\endgroup$ – WhatAMesh Aug 31 '18 at 14:05
1
$\begingroup$

Using bmesh.

enter image description here

Recommend using bmesh for this. Where instead of splines, use edges. The vertices in the same location as the spline points. Manipulate the bmesh via the API and then convert to curve. If there was a bcurve module would use that, but there isn't and curve edit operators are designed to drive one bonkers.

In the test script below, for part 1 I've used all of the string curves to make one bmesh. Would change up the creation script in question to create a bmesh in first place, for answer sake I've scripted to use the curves created via the script in question.

All edges which have a vertex in the positive quadrant are selected. The selection is then bisected by one of the 3 axis planes. The resulting geometry bisected again and again by other two axes. This will add a vertex on each edge that crosses the planes.

Next all edges that have both verts in the positive quadrant are removed and the subsequent mesh converted to a curve. I've used the operator here for example sake. Your question already displays how this is done via API.

The resulting curve object has multiple splines rather than being multiple objects, see note below.

import bpy
import bmesh

context = bpy.context
#PART 1 load all single splne curves to one bmesh
scene = context.scene
bm = bmesh.new()
curves = [o for o in scene.objects if o.type == 'CURVE']
for c in curves:
    verts = [bm.verts.new(p.co.xyz) for p in c.data.splines[0].points[1:-1]]
    for i in range(len(verts) - 1):
        bm.edges.new((verts[i], verts[i + 1]))
    bmesh.ops.transform(bm, verts=verts, matrix=c.matrix_world)

# select all edges with a vert in pos quadrant
def is_inquadrant(v):
    return all(getattr(v.co, axis)  >= -1e-4 for axis in "xyz")

geom = [e for e in bm.edges if any(is_inquadrant(v) for v in e.verts)]

# bisect the selection by three axis planes
ret = bmesh.ops.bisect_plane(bm, 
            geom=geom, 
            plane_no=(0, 0, 1), 
            plane_co=(0, 0, 0))
ret = bmesh.ops.bisect_plane(bm, 
            geom=ret['geom'], 
            plane_no=(0, 1, 0), 
            plane_co=(0, 0, 0))
ret = bmesh.ops.bisect_plane(bm, 
            geom=ret['geom'], 
            plane_no=(1, 0, 0), 
            plane_co=(0, 0, 0))
# Now have new verts on cuts, select edges with both verts in quad            
for v in bm.verts:
    v.select = False
for e in bm.edges:
    e.select = all(is_inquadrant(v) for v in e.verts)
# remove them    
geom = [e for e in bm.edges if e.select]
bmesh.ops.delete(bm, geom=geom, context=2)
me = bpy.data.meshes.new("BallEdgeMesh")
bm.to_mesh(me)

bm.free()

# add as mesh to scene.

ob = bpy.data.objects.new("BallMesh", me)
scene.objects.link(ob)
scene.objects.active = ob
ob.select = True
# convet to curve
bpy.ops.object.convert(target='CURVE')
curveData = context.object.data
curveData.dimensions = '3D'
curveData.resolution_u = 2
curveData.fill_mode = 'FULL'
curveData.bevel_depth = 0.02
for s in context.object.data.splines:
    s.use_smooth = True
    s.type = 'NURBS'

Note:

If you must have one object for each string: It would be more efficient to make only one "string" curve object (bpy.data.curves) and link to each object instance. Using methods similar to above would only need to add a new curve if it is bisected.

Finally welcome to bse, and kudos on writing a great first question.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.