SetTextureCoordinates - RhinoCommon + PY Script

Let’s go for the 40K then… that should be possible to zip and attach here or per PM right?
If you could pickle it, that might be the easiest for others to help figure out a solution.

Also: is there any documentation of the data structure coming from laubwerk?
That already might be enough to construct a parsing algorithm to create a Rhino Mesh.

-Willem

In general Laubwerk PY lib is embedded in every plant pack they release, so getting a free one is enough to have the PY loader, OBJ writer example, plus simplified documentation that is included in it as a simple HTML file - side note not all examples here seem to be updated uniformly so sometimes it needs small fixes. I tried to find legal info but it looks like I can share those docs:
laubwerk_py_docs.pdf (87.3 KB)

It said it cannot pickle it, so I used jsonpickle this way:

Here it is
simplest.json (1.6 MB)

Should result in: poly: 3958 and verts: 5590 and normals: 5590 and uvs: 13749

Thanks for the info.

At first glance it seems there is a list missing from your mesh data, the textverts described in the PDF

I interpret that as the texture list to map to the polygons.
That in turn references the uvs list.

I’m short in time today so I cannot dive into checking lbwtoobj.py to confirm my suspicion.

Well, yes I just wanted to keep it simple and trim not needed(?) data, if you look at it you’ll find out that those are not very helpful… Or my internal “Line” command fails to connect the dots.

At least if I understand this correctly and you iterate, the (i) does exactly the same in the whole puzzle like for face verts → i++ or if this seems confusing int position = 0; for each face vert ( use position; position++;). Isn’t it redundant?

So textverts look like the polygon(faces) list.
They have equal length and reference the same amount of indices?

I’m still confused about the Unwelding and when it is applied
How about this mapping then:

# Rhino mesh lists
rh_verts = []
rh_norms = []
rh_uvs = []
rh_faces = []

# extMData
# md_verts
# md_norms
# md_uvs
# polygons
# textverts

N = 0
for poly, text in zip(polygons, textverts):

    # compose rhino mesh lists
    rh_face = []
    for i in range(len(poly)):

        rh_face.append(N)

        v_index = poly[i]
        t_index = text[i]
        
        rh_verts.append(md_verts[v_index])
        rh_norms.append(md_norms[v_index])
        rh_uvs.append(md_uvs[t_index])
        N += 1

    rh_faces.append(rh_face)

Seems I’m not following your “stack” :sweat_smile: You are trying to reconstruct the unwelded mesh?

I called unweld right before:

for uv in lbw_mesh.uvs:
    mesh.TextureCoordinates.Add(Rhino.Geometry.Point2f(uv[0], uv[1]))

Here is “clean” code which I have:

#! python3

import rhinoscriptsyntax as rs
import scriptcontext as sc
import math

import System
import System.Collections.Generic
import Rhino

# import the laubwerk module
import laubwerk

try:
# load a laubwerk plant file
    lbw_plant = laubwerk.load("C:\path\to\Laubwerk\Plants\Acer_campestre\Acer_campestre.lbw.gz")
except IOError:
    #print ("Plant file is missing")
    Rhino.RhinoApp.WriteLine("Plant file is missing")
else:
# pick a specific model from the plant file
    lbw_model = lbw_plant.models[0]

# generate the actual tree geometry
lbw_mesh = lbw_model.get_mesh(qualifier="summer", max_branch_level=1, min_thickness=0.3, leaf_amount=0.1, leaf_density=0.1, max_subdiv_level=1)

print("Created LBW_MESH. It has poly: " + str(len(lbw_mesh.polygons)) + " and verts: " + str(len(lbw_mesh.points)) + " and normals: " + str(len(lbw_mesh.normals)) + " and uvs: " + str(len(lbw_mesh.uvs)) + " and texverts: " + str(len(lbw_mesh.texverts)))

mesh = Rhino.Geometry.Mesh();

for pt in lbw_mesh.points:
    mesh.Vertices.Add(pt[0],pt[1],pt[2])

for poly in lbw_mesh.polygons:
    if len(poly) == 3:
        mesh.Faces.AddFace(poly[0],poly[1],poly[2])
    elif len(poly) == 4:
        mesh.Faces.AddFace(poly[0],poly[1],poly[2],poly[3])
    else:
        print("Other poly not supported")

for nrm in lbw_mesh.normals:
    mesh.Normals.Add(nrm[0],nrm[1],nrm[2])

#Unweld to match uvs Count
mesh.Unweld(0, False)

# Clear existing texture coordinates if any
mesh.TextureCoordinates.Clear()

for uv in lbw_mesh.uvs:
    mesh.TextureCoordinates.Add(Rhino.Geometry.Point2f(uv[0], uv[1]))

#Change Y-Up to Z-Up
mesh.Rotate(math.radians(90),Rhino.Geometry.Vector3d(1,0,0),Rhino.Geometry.Point3d.Origin)

y, l = mesh.IsValidWithLog()
print(y, l)

print("Script executed sucessfully.")
print(sc.doc.Objects.AddMesh(mesh))
sc.doc.Views.Redraw()

At first glance I discarded texverts (probably due to the Thicket source reference), however, now I see texverts are ids of uvs per face which can possibly reverse to mesh polygons [A,B,C] or [A,B,C,D] so here is the possible way of getting back the unwelded thing… If my assumptions are correct…

When you call Rhino unweld you cannot rely on the indices being the same as you had them before the unweld. Hence doing “unweld” yourself like shown by @Willem is the way to go, since you have then control over indices.

It is essentially also what I was trying to say. You’ll have to set up these yourself.

Yup, probably the biggest issue here is simple human error on my side as being totally honest I just suck at PY :rofl:

Code looks fine enough when not looking at the problem at hand. You’re doing just fine there.

I think it’ll help if you draw out the data structures on paper with a simple cube with what you get and then draw out where you need to go. Then see how the data maps between the two. That should help gaining a better understanding.

If it were me I’d just write out an OBJ file and use the OBJ importer. There’s no real need to invent the same wheel in a different way if the current one does everything you need. But that is just my humble opinion.

1 Like

This is it! Zipping and nesting into mat_idx allowed me to import separately mesh piece by material.

Editing as answer to my question was obvious, seems I’m just tired today :slight_smile:

1 Like

@nathanletwory one quick question how to trigger clipping on texture, I mean original texture is a tif file with transparency. When I would add it normally Rhino auto picks it and clips alpha regions of it, but when I’m doing it via code it doesn’t clip it. So wheres the little nice switch to poke ? :smiley:

Though it looks like it is already using alpha :person_shrugging: :

Ok figured it out. It is material prop called AlphaTransparency

1 Like

General reminder about importing texture coordinates

When you import mesh geometry and want to set up a texture mapping as well it’s easiest to utilize the surface parameter mapping. You don’t need to add an actual texture mapping into the document because every new object has an implicit surface parameter mapping on channel 1. The surface parameter mapping needs per vertex surface parameters to be set up on the mesh geometry. At the moment easiest way to set them up is to first set them into the Mesh.TextureCoordinates and then call Mesh.SetSurfaceParametersFromTextureCoordinates.