6
$\begingroup$

A while back, I made a planet Earth (UV Sphere, 3 Subsurf levels, and highres Earth texture with Generated Texture Coords) with various pins stuck on different geographic locations. These pins each had a plane at the bottom of them with a unique picture on them. It was for a project I did for my dad on Fathers Day this year (I made a collage of all of his colleagues and him to show that they were a company that truly covered a lot of the world).

See second link for full video:

enter image description here

While I was making this, I devised a system that worked decently for manually placing different pins at different latitudes and longitudes on the "planet," which included placing the 3D Cursor at the center of the planet and rotating the pins around the planet with Pivot point > 3D Cursor. However, between rotating the pins correctly, setting different images, and then making minor corrections, this understandably took a long, long time, and if I had to do something like this again, I would prefer that it not take as long.

Gif of rotation with failed text :)

enter image description here

I'm wondering if anyone could design a Python script/add-on that would take the inputs Latitude, Longitude, and a Path to an image, grab a preset mesh (pin and plane), position it at the "latitude and longitude," and add the image to the plane. It occurs to me that the location would only have to be relative, because one could always use a Vector > Mapping to rotate the map image.

$\endgroup$
2
8
$\begingroup$

If I have understood your video well, the difficulties (I am learning Python) here are linked to several points :

  • Copy of objects and materials as I have seen no way to change the pictures and colors keeping original pin and card materials
  • Placement of the pin and card

Copy of objects and materials

Copy of objects is base on this function that creates a new mesh and a new object based on this mesh and link it to the scene. The object is tagged 'CivicCollage' as this will be useful for a cleanup of the generated object, if needed.

def Duplicate( name, obj ):
    newMesh = obj.data.copy()
    newObj = bpy.data.objects.new( name, newMesh )
    newObj.isCivicCollage = True
    bpy.context.scene.objects.link( newObj )
    return newObj

Copy of the materials needs to preserve all the object materials and replace only the needed one (either the texture for the card, or the color for the pin). The trick here is to link these materials at the object level, as if not, this will interact badly with the original objects.

def AssignDuplicateMat( obj, copyObj, mat ):
    newMat = mat.copy()
    newMat.isCivicCollage = True

    for i in range(len(obj.material_slots)):
        copyObj.material_slots[i].link = 'OBJECT'
        if obj.material_slots[i].material.name == mat.name:
            copyObj.material_slots[i].material = newMat
        else:
            copyObj.material_slots[i].material = obj.material_slots[i].material

    return newMat

Once done, using the previous function for both card and pin, we can change either the texture of the card or the color of the pin. This is based on the name of one node of the Cycles configuration :

def AssignColorToMat( mat, nodeName, color ):
    node = mat.node_tree.nodes.get( nodeName )
    if node != None:
        node.inputs['Color'].default_value = (color.r, color.g, color.b, 1)

def AssignTextureToMat( mat, nodeName, image ):
    if image:
        node = mat.node_tree.nodes.get( nodeName )
        node.image = image

The image is previously loaded from a file. We first check if this image is already known, and if not we load it using the combination of a root path and a file name to have the full image path :

def LoadImage( imagePath, fileName ):
    try:
        return bpy.data.images[fileName]
    except:
        try:
            fullFileName = bpy.path.abspath( imagePath + fileName )
            return bpy.data.images.load( fullFileName, check_existing = False )
        except:
            return None

Objects placement

We need some conventions here :

  • Pin and card are Z up (we see the pin top and the card face from top view)
  • World sphere is orientated so that 0° in latitude and longitude is at positive X axis

From that, we convert the parameters in radians, then calculate the position and finally the needed rotation (surely that could be shorter, but...). Note that the rotation needs to be assigned in the mode of the object rotation display (here I kept the conventional Euler, but if you are not using it, this code may fail, assigning the values but with no visible results).

def PlacePin( worldLocation, radius, latDeg, lonDeg, scale, pin, card ):

    latRad, lonRad = radians( latDeg ), radians( lonDeg )

    x = cos( latRad ) * cos( lonRad )
    y = cos( latRad ) * sin( lonRad )
    z = sin( latRad )

    location = Vector( (x, y, z) ) * radius
    locationFromWorld = location + worldLocation
    scale3D = (scale, scale, scale)

    aroundZ = Quaternion( (0, 0, 1), lonRad )
    aroundY = Vector( (0, -1, 0) )
    aroundY.rotate( aroundZ )
    aroundX = Quaternion( aroundY, latRad )

    rotation = Quaternion( baseRotationQuat )
    rotation.rotate( aroundZ )
    rotation.rotate( aroundX )

    pin.scale = scale3D
    pin.location = locationFromWorld
    pin.rotation_euler = rotation.to_euler( 'XYZ' )

    card.scale = scale3D
    card.location = locationFromWorld
    card.rotation_euler = rotation.to_euler( 'XYZ' )

Other aspects

I can't detail all the script parts here.

The addon is based on a GUI with two modes.

  • A manual mode

These parameters are self explanatory, except maybe the nodes : you have to set here the nodes names (as they appear in the node information panel 'N').

enter image description here

  • A "from file" mode

This mode allows you to generate several pins from a file.

This file is indicated in the first parameter and the last parameter corresponds to the images folder.

enter image description here

For instance, I have used this text file, with fields separated by ';' and with the following columns:

  • Latitude
  • Longitude
  • Scale
  • R color component of the pin
  • G color component
  • B color component
  • Location name
  • Image file name

    48.8534100;2.3488000;0.05;255;0;0;Paris;paris.jpg

    51.500152;-0.126236;0.05;255;0;0;London;london.jpg

    40.440624;-79.995888;0.2;0;255;0;Pittsburgh;pittsburgh.jpg

    35.6895000;139.6917100;0.1;0;255;0;Tokyo;tokyo.jpg

    -22.9027800;-43.2075000;0.1;0;255;0;Rio;brazil.jpg

    6.465422;3.406448;0.1;0;255;0;Lagos;lagos.jpg

    39.9075000;116.3972300;0.1;0;255;0;Pekin;pekin.jpg

    -35.2834600;149.1280700;0.1;0;255;255;Canberra;sydney.jpg

Note about the file data : I needed to add a carriage return for the presentation of the answer, but for the real file keep the lines consecutive (the real file is packed into the blend).

  • Cleaning the generated objects

Last, the "clean" button allows to delete generated objects and to mark unlinked materials and textures. This can be useful also because the script may fail if some parameters are not as expected...

Addon

To have an addon from the blend file below, simply save the Python code into a file.py. Or simply run the script directly from Blender.

Note that I have tested all that... but if you encounter some problem, please tell...

$\endgroup$
2
  • 1
    $\begingroup$ great answer going above and beyond .... could use blender.stackexchange.com/questions/19533/… from the surface - origin vector to align the objects. Also bpy.props.FloatProperty(subtype='ROTATION') will remove the need for the radians conversion. $\endgroup$ – batFINGER Aug 31 '16 at 13:30
  • $\begingroup$ @batFINGER, thanks ! exactly what I wanted to do knowing x, y, z !! $\endgroup$ – lemon Aug 31 '16 at 13:33

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.