0
$\begingroup$

I want to fill a bookshelf with random instances of books. The books are available as instance collections and vary in size.

I believe array modifiers cannot work with instances, and cannot pick from multiple objects anyway.

I am guessing there is a way to do it with geometry nodes, but I am not sure how to capture the dimensions of each book and then place the next instance accordingly.

Is there any way to do this procedurally? Or do I need to create manually pre-randomized instances?

$\endgroup$
1
  • 2
    $\begingroup$ You can find a vertical version of the bulk of this problem here :) $\endgroup$
    – Robin Betts
    Apr 2 at 20:48

1 Answer 1

1
$\begingroup$

Here's a cute way to do this in python scripting, only from a single collection:

enter image description here

import bpy,random

bookYlen=0.0
bookCnt = len(bpy.data.collections['book_collection'].objects)
maxAttempts = bookCnt * 2 ## allow for dups in randon selection, up to a point 
shelf = bpy.data.objects['shelf']
bpy.ops.collection.create(name='placed books')
bpy.context.scene.collection.children.link( bpy.data.collections['placed books'] )
bookNextYloc = shelf.location.y - (shelf.dimensions.y / 2)

# keep choosing and plancing an object in collection until total width will exceed
while bookYlen < (shelf.dimensions.y - .16):
    attempts = 0  # 
    print( 'bookYlen',bookYlen)
    while attempts <= maxAttempts:
        attempts += 1
        # get a random book
        chosenIdx = random.randint( 0, bookCnt - 1 )
        chosen = bpy.data.collections['book_collection'].objects[ chosenIdx ]
        widthToAdd = chosen.dimensions.y
        print( 'attempts',attempts,'maxAttempts',maxAttempts, 'try adding ', widthToAdd, 'at',bookYlen, 'with shelf.dimensions.y', shelf.dimensions.y)
        if ((bookYlen + widthToAdd) > shelf.dimensions.y):
            print( 'filling up ... try another' )
            continue # hope for another, slimmer book
        # place (a linkded duplicate of) this book on the shelf
        chosen.select_set( True )
        bpy.ops.object.duplicate_move_linked()
        chosen.select_set( False )
        # created object is the new book to place
        book1 = bpy.context.selected_objects[0]
        book1.select_set( True )
        # assume x and z axis are aligned
        book1.location.z = shelf.location.z
        book1.location.x = shelf.location.x
        # place at next open position
        book1.location.y = bookNextYloc
        print( book1.name, 'placed at ', bookYlen )
        bookYlen += book1.dimensions.y
        bookNextYloc += book1.dimensions.y
        # move to "placed" collection
        bpy.data.collections['placed books'].objects.link( book1 )
        bpy.data.collections['book_collection'].objects.unlink( book1 )
        book1.select_set( False )
        

$\endgroup$
4
  • 1
    $\begingroup$ Thank you, I will try to find a way to do this with geometry nodes, but this script may yet end up being useful anyway. $\endgroup$
    – cornergraf
    Apr 3 at 5:30
  • $\begingroup$ @cornergraf -- while i'm not a smart GN user, I agree that scripting aught to be a last choice. $\endgroup$
    – james_t
    Apr 4 at 15:47
  • 1
    $\begingroup$ I actually ended up using your script, which I modified a bit to suit my purposes. I have an almost working geometry nodes solution, which I also hope to get working eventually. I will post everything here under this question once I got it working. $\endgroup$
    – cornergraf
    Apr 6 at 9:30
  • $\begingroup$ @cornergraf -- i hope you get a chance to "mark as solution" so I raise my reputation on this site. I work for points, LOL. $\endgroup$
    – james_t
    Apr 6 at 16:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .