Sketch render script

Sketch render script

bryan_martin_morris
Contributor Contributor
811 Views
6 Replies
Message 1 of 7

Sketch render script

bryan_martin_morris
Contributor
Contributor

Hi all,

 

Not sure this is of interest for anyone, considering that usually VRED is intended to make things look photo real. But  I have been in need of a one stop solution to doing some renders/videos that have a technical drawing aesthetic, that can be easily mapped over or below photo real renders with appropriately visible details.

 

To do so I have made a quite poor but somewhat functional script.

 

It essentially recreates the style of the non-realistic renderer, but you can ray-trace the result. It requires opencv-python and numpy to be installed.

 

I convert NURBS to mesh (and rename them as otherwise bad things happen), then use the UV editor to make a snapshot, then modify the image to black lines, thicken them, and then UV map the result. I also make material switches so that the effect can be switched using another script... but this makes a mess of the material editor, as it makes a material, and switch per part...!

 

I don't think it's very efficient, and the results can be a bit hit and miss (some parts seem to require multiple dilate iterations... see the cab and hydraulics). 

digger.png

 

But I think the result kind of cool, and roughly works for what I want it to... It just takes an age to do more complex models, with the UV editor throwing some errors, and VRED becoming unresponsive for a while (It took roughly 4 minutes for the digger).

 

No idea if its useful to anyone, but if so... great. If anyone has any ideas for an approach that could do this more efficiently, I'd be interested.

 

Cheers.

 

import numpy as np
import cv2 as cv

def sketcherize(node_group):

    root_node = vrNodeService.findNode(node_group)
    
    geo_nodes = [child for child in root_node.getChildrenRecursive() if child.isType(vrdGeometryNode)]

    nodes = [geo_node for geo_node in geo_nodes if geo_node.isShell()]
    
    for node in nodes:
        if not node.isMesh():
            vrScenegraphService.convertToMesh([node]) 
    
    map_size = 2056
    resolution = 1024
    padding = 10 #pixels
    padding /= map_size #UV 0.0-1
    
    save_path = 'YOUR PATH GOES HERE :)'  

    layout_settings = vrdUVLayoutSettings()
    layout_settings.setIslandPadding(padding)
    layout_settings.setTilePadding(padding)
    layout_settings.setResolution(resolution)
        
    unfold_settings = vrdUVUnfoldSettings()
    unfold_settings.setMapSize(map_size)
    
    vrUVService.unfold(nodes, unfold_settings, layout_settings, vrUVTypes.UVSet.MaterialUVSet)
    
    sketch_material_group_list = [] 
    sketch_material_group = vrMaterialService.createMaterialGroup()
    sketch_material_group.setName('sketch_material_group')
    
    for node in nodes:
        node.setName(f'{node.getName()}_{node.getObjectId()}')

        uv_path=f'{save_path}{node.getName()}.png'
        vrUVService.saveUVSnapshot(
                        [node], 
                        uv_path,
                        2056,2056,
                        vrUVTypes.UVSet.MaterialUVSet,
                        vrUVTypes.ShowBorders)
        # read image into openCV
        image = cv.imread(uv_path)

        # convert img to grayscale
        gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)

        # make anything not white into black
        mask = gray.copy()
        mask[mask!=0] = 255        
        
        # do this...
        kernel = np.ones((5,5),np.uint8)
        
        # grow the mask (1 times but can be reduced/increased depending on the requirements).
        dilate = cv.dilate(mask, kernel, iterations=1)
                
        # invert to black on white
        inverted = cv.bitwise_not(dilate)
        
        # overwrite the original
        cv.imwrite(uv_path, inverted)
        
        old_mat = node.getMaterial()
        new_mat = vrMaterialService.createMaterial(
            f'{node.getName()}_sketchUV',
            vrMaterialTypes.MaterialType.Plastic,
            sketch_material_group)
        sketch_texture = vrImageService.loadImage(uv_path)
        dif_texture = new_mat.getDiffuseTexture()
        dif_color = QVector3D(1.,1.,1.)
        new_mat.setDiffuseColor(dif_color)
        
        dif_texture.setImage(sketch_texture)
        dif_texture.setUseTexture(True)
        dif_texture.setUseAlpha(False)
        dif_texture.setMappingType(vrTextureTypes.MappingType.UVMapping)
        
        
        mat_switch = vrMaterialService.createSwitchMaterial([new_mat,old_mat],sketch_material_group)
        mat_switch.setName(f'{node.getName()}_sketchUV_material_switch')
        
        node.applyMaterial(mat_switch)

  

812 Views
6 Replies
Replies (6)
Message 2 of 7

Christian_Garimberti
Advisor
Advisor

Hi @bryan_martin_morris i will take a look at your script! it seems to be a very useful thing thinking about industrial staff that not alwaysneed a "real" render.

And if i'll find some improvements i will send it you back

Thank you for sharing!

Chris

Christian Garimberti
Technical Manager and Visualization Enthusiast
Qs Informatica S.r.l. | Qs Infor S.r.l. | My Website
Facebook | Instagram | Youtube | LinkedIn

EESignature

Message 3 of 7

cikho
Collaborator
Collaborator

well this look pretty cool.

making such renders, even in the NPR mode, is always a pain.

_________________________________________________________

almost a showcase PRO user 😛

if you find my post useful, please give kudos or mark as solution 😄
0 Likes
Message 4 of 7

Christian_Garimberti
Advisor
Advisor

Hi @bryan_martin_morris , this is my first upgrade to your script.

I managed part dimensions to pilot maps dimension, so more or less every part have the same "line" resolution.

Maybe it is not faster but slower, since i had to unfold every single part and scan the whole model to find the biggest part as a reference.

Christian_Garimberti_0-1689694891699.png

 

I added an options to reuse the existing uvmaps if there are.

Let me know.

Best

Chris

import numpy as np
import cv2 as cv

# setting for the UV map size referred to how big is the selection
maxUVmapSize = 2048
coeffResolution = 2
pixelPadding = 10  #pixels
UVmapSavePath = 'c:/temp/UVMaps/'  
UseExistingMaps = False

def sketcherize(node_group):
    
    if not node_group:
        root_node = vrScenegraphService.getSelectedNode()
    else: 
        root_node = vrNodeService.findNode(node_group)
        
    # scan the model to find the geo and convert them to shell
    geo_nodes = [child for child in root_node.getChildrenRecursive() if child.isType(vrdGeometryNode)]
    for geo_node in geo_nodes:
        if not geo_node.isMesh():
            vrScenegraphService.convertToMesh([geo_node])
    nodes = [geo_node for geo_node in geo_nodes]
    # print(nodes)
    
    # try to find the biggest part to be used as a reference
    biggestNode = ""
    refMeasure = 0
    for node in nodes:
        bBox = node.getBoundingBox()
        bBoxSize = bBox.getSize()
        nodeRefMeasure = max([bBoxSize.x(), bBoxSize.y(), bBoxSize.z()])
        if nodeRefMeasure > refMeasure:
            refMeasure = nodeRefMeasure
            biggestNode = node.getName()
    print("Biggest Node: " + biggestNode + " - Reference Measure= " + str(refMeasure))
    
    # try to understand how big is the selection to compare with the UV map Max Size
    #bBox = root_node.getBoundingBox()
    #bBoxSize = bBox.getSize()
    # Search the max extents to use it as a reference
    #refMeasure = max([bBoxSize.x(), bBoxSize.y(), bBoxSize.z()])
    # calc a parameter to use to manage sizes for every shell
    if refMeasure == 0:
        refMeasure = 1000
    UVSizeCoeff = maxUVmapSize/refMeasure
    print("UVMapCoeff: " + str(UVSizeCoeff))
    
    sketch_material_group_list = []
    sketch_material_group = vrMaterialService.createMaterialGroup()
    sketch_material_group.setName('sketch_material_group')
    
    layout_settings = vrdUVLayoutSettings()
    unfold_settings = vrdUVUnfoldSettings()
    
    for node in nodes:
        curNodeName = node.getName()
        curNodeOId = node.getObjectId()
        if curNodeName.find("_" + str(curNodeOId)) < 0:
            node.setName(f'{node.getName()}_{node.getObjectId()}')
        curNodeName = node.getName()
        
        # get the BBox to get the measures
        bBox1 = node.getBoundingBox()
        bBoxSize1 = bBox1.getSize()
        refMeasure1 = max([bBoxSize1.x(), bBoxSize1.y(), bBoxSize1.z()])
        nodeUVSize = refMeasure1 * UVSizeCoeff
        print(curNodeName + ": MaxBB = " + str(refMeasure1) + " - UvSize = " + str(nodeUVSize))
        
        map_size = nodeUVSize
        resolution = nodeUVSize/coeffResolution
        padding = pixelPadding
        padding /= map_size #UV 0.0-1
        
        
        layout_settings.setIslandPadding(padding)
        layout_settings.setTilePadding(padding)
        layout_settings.setResolution(resolution)
        unfold_settings.setMapSize(map_size)
        
        isToUnfold = True
        if UseExistingMaps and node.hasUVSet(vrUVTypes.UVSet.MaterialUVSet):
            isToUnfold = False
        if isToUnfold:
            print("Unfolding " + curNodeName)
            vrUVService.unfold([node], unfold_settings, layout_settings, vrUVTypes.UVSet.MaterialUVSet)

        uv_path=f'{UVmapSavePath}{curNodeName}.png'
        vrUVService.saveUVSnapshot(
                        [node], 
                        uv_path,
                        map_size,map_size,
                        vrUVTypes.UVSet.MaterialUVSet,
                        vrUVTypes.ShowBorders)
        # read image into openCV
        image = cv.imread(uv_path)

        # convert img to grayscale
        gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)

        # make anything not white into black
        mask = gray.copy()
        mask[mask!=0] = 255        
        
        # do this...
        kernel = np.ones((5,5),np.uint8)
        
        # grow the mask (1 times but can be reduced/increased depending on the requirements).
        dilate = cv.dilate(mask, kernel, iterations=1)
                
        # invert to black on white
        inverted = cv.bitwise_not(dilate)
        
        # overwrite the original
        cv.imwrite(uv_path, inverted)
        
        old_mat = node.getMaterial()
        new_mat = vrMaterialService.createMaterial(
            f'{node.getName()}_sketchUV',
            vrMaterialTypes.MaterialType.Plastic,
            sketch_material_group)
        sketch_texture = vrImageService.loadImage(uv_path)
        dif_texture = new_mat.getDiffuseTexture()
        dif_color = QVector3D(1.,1.,1.)
        new_mat.setDiffuseColor(dif_color)
        
        dif_texture.setImage(sketch_texture)
        dif_texture.setUseTexture(True)
        dif_texture.setUseAlpha(False)
        dif_texture.setMappingType(vrTextureTypes.MappingType.UVMapping)
        
        
        mat_switch = vrMaterialService.createSwitchMaterial([new_mat,old_mat],sketch_material_group)
        mat_switch.setName(f'{node.getName()}_sketchUV_material_switch')
        
        node.applyMaterial(mat_switch)
        
# sketcherize(None)

 

 

Christian Garimberti
Technical Manager and Visualization Enthusiast
Qs Informatica S.r.l. | Qs Infor S.r.l. | My Website
Facebook | Instagram | Youtube | LinkedIn

EESignature

Message 5 of 7

seiferp
Community Manager
Community Manager

Cool stuff you are doing there ✌️

Message 6 of 7

bryan_martin_morris
Contributor
Contributor

@Christian_Garimberti I like the plan to compare sizes and then even out the line weights 🙂 . I'll give it a go on my home workstation as soon as I get a chance.

I'm not convinced my choice of opencv is the best solution for the image work, I just used it as I have some other half-finished projects where it was useful. The reason I wonder is that I think the image files could probably benefit from some anti-aliasing but I guess robots and computers don't have much use for anti-aliased images.

Message 7 of 7

Christian_Garimberti
Advisor
Advisor

Hi @bryan_martin_morris, another small step...

Managed the reuse of materials, sketched and switch, to avoid the creation of multiple and multiple materials

Managed cloned nodes to avoid multiple unfolds

Created 2 variantset to switch between realistic and scketched view

Just 1 thing i miss, maybe @seiferp could help. i cannot find API to search and find material group folders in the material browser. so every time the procedure starts, one new group, maybe empty is created.

import numpy as np
import cv2 as cv

# setting for the UV map size referred to how big is the selection
maxUVmapSize = 2048
coeffResolution = 2
pixelPadding = 10  #pixels
UVmapSavePath = 'c:/temp/UVMaps/'  
UseExistingMaps = False

def sketcherize(node_group):
    
    if not node_group:
        root_node = vrScenegraphService.getSelectedNode()
    else: 
        root_node = vrNodeService.findNode(node_group)
        
    # scan the model to find the geo and convert them to shell
    geo_nodes = [child for child in root_node.getChildrenRecursive() if child.isType(vrdGeometryNode)]
    for geo_node in geo_nodes:
        if not geo_node.isMesh():
            vrScenegraphService.convertToMesh([geo_node])
    nodes = [geo_node for geo_node in geo_nodes]
    # print(nodes)
    
    # try to find the biggest part to be used as a reference
    biggestNode = ""
    refMeasure = 0
    for node in nodes:
        bBox = node.getBoundingBox()
        bBoxSize = bBox.getSize()
        nodeRefMeasure = max([bBoxSize.x(), bBoxSize.y(), bBoxSize.z()])
        if nodeRefMeasure > refMeasure:
            refMeasure = nodeRefMeasure
            biggestNode = node.getName()
    print("Biggest Node: " + biggestNode + " - Reference Measure= " + str(refMeasure))
    
    # try to understand how big is the selection to compare with the UV map Max Size
    #bBox = root_node.getBoundingBox()
    #bBoxSize = bBox.getSize()
    # Search the max extents to use it as a reference
    #refMeasure = max([bBoxSize.x(), bBoxSize.y(), bBoxSize.z()])
    # calc a parameter to use to manage sizes for every shell
    if refMeasure == 0:
        refMeasure = 1000
    UVSizeCoeff = maxUVmapSize/refMeasure
    print("UVMapCoeff: " + str(UVSizeCoeff))
    
    # creation of the material Folder
    sketch_material_group_list = []
    sketch_material_group = vrMaterialService.createMaterialGroup()
    sketch_material_group.setName('sketch_material_group')
    
    # creation of the varianSet
    variantSetSketchName = "Sketcherized"
    variantSetRealName = "Realistic"
    vrVariantSets.deleteVariantSet(variantSetSketchName)
    vrVariantSets.deleteVariantSet(variantSetRealName)
    sketchVariant = vrVariantSets.createVariantSet(variantSetSketchName)
    realVariant = vrVariantSets.createVariantSet(variantSetRealName)
    sketchVariant.addScript('applySketchedView()')
    realVariant.addScript('applyrealisticView()')
    
    # base unfold settings
    layout_settings = vrdUVLayoutSettings()
    unfold_settings = vrdUVUnfoldSettings()
        
    # for each node...
    unfoldedNodes = []
    for node in nodes:
        curNodeName = node.getName()
        curNodeOId = node.getObjectId()
        if curNodeName.find("_#") < 0:
            node.setName(f'{node.getName()}_#{node.getObjectId()}')
        curNodeName = node.getName()
        
        # get the BBox to get the measures
        bBox1 = node.getBoundingBox()
        bBoxSize1 = bBox1.getSize()
        refMeasure1 = max([bBoxSize1.x(), bBoxSize1.y(), bBoxSize1.z()])
        nodeUVSize = refMeasure1 * UVSizeCoeff
        print(curNodeName + ": MaxBB = " + str(refMeasure1) + " - UvSize = " + str(nodeUVSize))
        
        map_size = nodeUVSize
        resolution = nodeUVSize/coeffResolution
        padding = pixelPadding
        padding /= map_size #UV 0.0-1
        
        layout_settings.setIslandPadding(padding)
        layout_settings.setTilePadding(padding)
        layout_settings.setResolution(resolution)
        unfold_settings.setMapSize(map_size)
        
        isToUnfold = True        
        if UseExistingMaps and node.hasUVSet(vrUVTypes.UVSet.MaterialUVSet):
            isToUnfold = False
        if isToUnfold:
            # check if it was already unfolded
            if curNodeName in unfoldedNodes:
                print("Skipped Unfold. Node already Unfolded: " + curNodeName)
            else:
                print("Unfolding " + curNodeName)
                vrUVService.unfold([node], unfold_settings, layout_settings, vrUVTypes.UVSet.MaterialUVSet)
                # insert in a list the unfolded nodes, to avoid to unfold many times the cloned nodes
                unfoldedNodes.append(curNodeName)
            
        uv_path=f'{UVmapSavePath}{curNodeName}.png'
        vrUVService.saveUVSnapshot(
                        [node], 
                        uv_path,
                        map_size,map_size,
                        vrUVTypes.UVSet.MaterialUVSet,
                        vrUVTypes.ShowBorders)

        # read image into openCV
        image = cv.imread(uv_path)
        # convert img to grayscale
        gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
        # make anything not white into black
        mask = gray.copy()
        mask[mask!=0] = 255        
        # do this...
        kernel = np.ones((5,5),np.uint8)        
        # grow the mask (1 times but can be reduced/increased depending on the requirements).
        dilate = cv.dilate(mask, kernel, iterations=1)
        # invert to black on white
        inverted = cv.bitwise_not(dilate)
        # overwrite the original
        cv.imwrite(uv_path, inverted)
        
        # create the material and the switch
        old_mat = node.getMaterial()
        newMatName = curNodeName + '_sketchUV'
        new_mat = vrMaterialService.findMaterial(newMatName)
        if new_mat and new_mat.isValid():
            print("Meterial " + newMatName + " Found.")
        else:
            new_mat = vrMaterialService.createMaterial(newMatName, vrMaterialTypes.MaterialType.Plastic, sketch_material_group)
            print("Meterial " + newMatName + " Created.")
        sketch_texture = vrImageService.loadImage(uv_path)
        dif_texture = new_mat.getDiffuseTexture()
        dif_color = QVector3D(1.,1.,1.)
        new_mat.setDiffuseColor(dif_color)
        
        dif_texture.setImage(sketch_texture)
        dif_texture.setUseTexture(True)
        dif_texture.setUseAlpha(False)
        dif_texture.setMappingType(vrTextureTypes.MappingType.UVMapping)
        
        matSwitchName = curNodeName + '_sketchUV_material_switch'
        mat_switch = vrMaterialService.findMaterial(matSwitchName)
        if mat_switch and mat_switch.isValid():
            print("material Switch " + matSwitchName + " Found.")
        else:
            mat_switch = vrMaterialService.createSwitchMaterial([new_mat, old_mat], sketch_material_group)
            mat_switch.setName(matSwitchName)
            print("material Switch " + matSwitchName + " Created.")        
        node.applyMaterial(mat_switch)
        
# sketcherize(None)

def applySketchedView():
    print("Sketched VariantSet")
    root_node = vrScenegraphService.getSelectedNode()
    nodes = [child for child in root_node.getChildrenRecursive()]
    for node in nodes:
        curMat = node.getMaterial()
        if curMat and curMat.isValid() and curMat.isType(vrdSwitchMaterial):
            #print(node.getName() + " - " + curMat.getName() + " - " + str(curMat.isType(vrdSwitchMaterial)))
            curMat.setChoice(0)
    print("Sketched VariantSet Applied")
    
def applyrealisticView():
    print("Realistic VariantSet")
    root_node = vrScenegraphService.getSelectedNode()
    nodes = [child for child in root_node.getChildrenRecursive()]
    for node in nodes:
        curMat = node.getMaterial()
        if curMat and curMat.isValid() and curMat.isType(vrdSwitchMaterial):
            #print(node.getName() + " - " + curMat.getName() + " - " + str(curMat.isType(vrdSwitchMaterial)))
            curMat.setChoice(1)
    print("Realistic VariantSet Applied")

 

best

Chris

 

 

Christian Garimberti
Technical Manager and Visualization Enthusiast
Qs Informatica S.r.l. | Qs Infor S.r.l. | My Website
Facebook | Instagram | Youtube | LinkedIn

EESignature