Hey polycount community!
I was wondering if there is a script/tool which allows the user to automatically generate smoothing groups from UVW seams in 3ds max. That should help to quickly bake normalmaps without weird seams.
I hope you can help

Cheers sebi
Replies
The opposite should be possible as well, I will have a look at it later and see if I can wrap together a quick script that does it the other way around.
In TexTools its the subscript 'fn_34__split_uv_by_smoothing_groups.ms' does it the other way you want it- I'll post it here as a reference for me later:
fn fn_34__split_uv_by_smoothing_groups =( function split_smoothingGroups obj uv=( print("process. "+(classOf obj.baseObject) as string); if (classOf obj.baseObject == Editable_Poly)then( --format "continue. n"; function getSmoothGroups _obj =( local numFaces = _obj.EditablePoly.GetNumFaces(); local numSmoothGroups = 0; local notfound = true; for i in 1 to numFaces while notfound do( local face_SG = polyop.getFaceSmoothGroup _obj i; if face_SG == 0 then( notfound = false; ) numSmoothGroups = amax #(face_SG, numSmoothGroups); ) local SmoothElements = #(); if (notfound == true)then( --setup the group arrays for i in 1 to numSmoothGroups do( SmoothElements[i]= #{}; ) ---- for i in 1 to numFaces do( local face_SG = polyop.getFaceSmoothGroup _obj i; SmoothElements[face_SG]+= #{i}; ) ) return SmoothElements; ) --set the smoothing groups local fcs = obj.EditablePoly.GetNumFaces(); local groups= getSmoothGroups obj; if (groups.count == 0)then( --no existing smoothing groups, so define some in the editAble Poly Object messagebox "You dont have smoothing groups on your model to split into. So it will be split with a default of 60 degrees" --format "no smoothing groups!n"; modPanel.addModToSelection (Edit_Poly ()) ui:on; local ePoly = obj.modifiers[#Edit_Poly]; fcs = ePoly.GetNumFaces(); ePoly.SetSelection #Face #{1..fcs} ePoly.autoSmoothThreshold = 60.00; ePoly.ButtonOp #Autosmooth ePoly.SetSelection #Face #{}--select nothing groups= getSmoothGroups obj; deleteModifier obj 1;--delete the top modifier uv.unwrap.edit();--back to the UV editor subobjectLevel = 3; uv.unwrap2.setTVSubObjectMode 3; ) for sel in groups do( if ((maxVersion())[1] >= 10000 )then( uv.unwrap6.selectFacesByNode sel obj; )else( uv.unwrap2.selectFaces sel; ) uv.unwrap5.quickPlanarMap();--quick flatten --uv.relaxByEdgeAngle 1 0 1 false; uv.unwrap5.relaxByFaceAngle 1 0 1 false; if ((maxVersion())[1] >= 10000 )then(--de select any faces, max2008 multi obj bug I guess uv.unwrap6.selectFacesByNode #{} obj; ) ) uv.unwrap2.selectFaces #{1..fcs}; --fn_21__normalize_uv_shells(); uv.unwrap2.pack 1 (0.01) true true false; uv.unwrap2.selectFaces #{}; ) ) if (selection.count > 0)then(--at least an object selected --local obj = selection[1]; local uv = modPanel.getCurrentObject(); if( classof(uv) == Unwrap_UVW)then( if ((maxVersion())[1] >= 10000 )then( for sel in selection do( split_smoothingGroups sel uv; ) )else( split_smoothingGroups selection[1] uv;--only do this with the first object in the selection ) ) ) )Prior to having it I'd use TexTool's UV->3d and then manually go and assign smoothing groups to each UV piece, then use it again to put it back together.
Hey,
thanks in advance! Sounds like a plan
here is the script:
function smoothByUVShells=( clearListener(); if (getCommandPanelTaskMode() != #modify)then(--make sure we are in the modify panel section setCommandPanelTaskMode #modify; ) if (selection.count == 1)then(--at least an object selected local obj = selection[1]; local uv = modPanel.getCurrentObject(); if (classof(uv) != Unwrap_UVW)then( modPanel.addModToSelection (Unwrap_UVW ()) ui:on; uv = modPanel.getCurrentObject(); ) uv.unwrap.edit(); uv.unwrap.edit(); uv.unwrap2.setTVSubObjectMode(3); local totalFaces = uv.unwrap.numberPolygons(); local faceElemArray = #(); for f=1 to totalFaces do ( faceElemArray[ f ] = 0; ) local elem = #(); --with redraw off; for f=1 to totalFaces do ( if faceElemArray[ f ] == 0 then ( uv.unwrap2.selectFaces #{ f }; uv.unwrap2.selectElement(); local elemFaces = uv.unwrap2.getSelectedFaces() as array; append elem (uv.unwrap2.getSelectedFaces()); for i in elemFaces do ( faceElemArray[ i ] = elem.count; -- Mark these vertices with their element number in vertElemArray. ) ) ) print("num shells: "+elem.count as string+"t"+totalFaces as string); modPanel.addModToSelection (Edit_Poly ()) ui:on; obj.modifiers[#Edit_Poly].autoSmoothThreshold = 180 for e in elem do( obj.modifiers[#Edit_Poly].SetSelection #Face e; obj.modifiers[#Edit_Poly].ButtonOp #Autosmooth ) ) ) smoothByUVShells();just open the maxscript Listener (F11 in 3dsmax) and go: File > New Script...
within that new script document paste the listed script and then go:
Tools > Evaluate All
or hit alternatively CTRL + E
To use it for objects just make sure:
The way it works right now is that it will add a UVunwrapModifier (if not already on top of the stack) to determine the UV shells (which faces belong in a group) and after that it adds a editPoly modifier to assign the smoothing groups for the face selections.
Will add it in the next release of TexTools, until then and otherwise simply use this script and or extend it to a macroscript.
PS: Would you mind if i send you a pm here at polycount if I have other ideas for your textool plugin?
I won't take your other stuff, hehe. I did look at your Edit UV code last night though, to see how max 2008+ handles multi object unwrap. I made an Edit UV button for my bar too, and tried to make it pretty smart.
It was breaking in my 1.2.4 build, so I got that all good now I think.
I've tried this.. and it doesn't work for 1 of the object of the 2 in the unwrap, but if I select faces on both, it will.
local CurSelLevel = selection[1].Unwrap_UVW.getTVSubObjectMode() uv = modPanel.getCurrentObject(); local uvFaces = uv.unwrap2.getSelectedFaces() as array if CurSelLevel == 3 and uvFaces.count > 0 then ( uv.unwrap2.selectElement(); )thanks
It help you to bake perfect normals. When uv is broken/splited there MUST also be break/split on smoothing groups.
thx
When finished - I'll share for sure!
Pure win, as always Renderhjs. thanks
1) Select UV borders (used part of Renderhjs' script for Roadkill uwrapping)
2) Convert UV edges selection to EditPoly edges selection (Max is a real pain in the ass!) - my part of the script
3) Make smoothing groups in hard edges style (used script by Pier Janssen).
That's it
macroScript UV_SHELL_BORDER category:"XSI2MAX_TRANS" ButtonText:"UV2HE" toolTip:"Convert selected UV shells to borders" ( ---------------------------------------------------------------------- ------------ Pier Janssen's Hard/Soft edge scripts ------------- ---------------------------------------------------------------------- struct hardSoftEdge ( -- --Get smoothing group integer as bitarray. --Not used in this script, but can be useful for testing. -- function getBitsAsArray bits = ( arr = #{} for i = 1 to 32 do ( arr[i] = bit.get bits i; ) return arr; ), -- --Get shared smoothing groups between two faces. -- function getSharedSmGroups obj faces = ( local smGroups = #(); for f in faces do ( append smGroups (polyOp.getFaceSmoothGroup obj f); ) local sharedSmGroups = (bit.and smGroups[1] smGroups[2]); return sharedSmGroups; ), -- --Get adjacent faces. -- function getAdjacentFaces obj face = ( local vertList = polyOp.getVertsUsingFace obj face; local adjacentFaces = polyOp.getFacesUsingVert obj vertList; deleteItem adjacentFaces face; return adjacentFaces; ), -- --Removes any smoothing groups that are not shared between the given --face or any of the adjacent faces. -- function removeRedundantGroups obj face = ( local adjacentFaces = hardSoftEdge.getAdjacentFaces obj face; local nonRedundantGroups = 0; for a in adjacentFaces do ( nonRedundantGroups = (bit.or nonRedundantGroups (hardSoftEdge.getSharedSmGroups obj #{face, a})); ) polyOp.setFaceSmoothGroup obj face nonRedundantGroups; ), -- --SET SOFT EDGE -- function setSoftEdge obj faces = ( --Get shared smoothing groups. local sharedSmGroups = (hardSoftEdge.getSharedSmGroups obj faces); --If there are no shared smoothing groups, continue. Otherwise, edge is already soft. if sharedSmGroups == 0 do ( --Get adjacent faces. local adjacentFaces = #(); for f in faces do ( append adjacentFaces (hardSoftEdge.getAdjacentFaces obj f); ) adjacentFaces[1] = adjacentFaces[1] - faces; adjacentFaces[2] = adjacentFaces[2] - faces; --Determine impossible smoothing groups. local impSmGrps = 0; local c = 1; for f in faces do ( --Get current face smoothing group. local smGrpCurFace = (polyOp.getFaceSmoothGroup obj f); --Get adjacent non-shared smoothing groups and add them to impSmGrps. for a in adjacentFaces[c] do ( local smGrpAdjFace = (polyOp.getFaceSmoothGroup obj a); local nonShared = (bit.and smGrpAdjFace (bit.not smGrpCurFace)); impSmGrps = (bit.or impSmGrps nonShared); ) c += 1; ) --Check for first available smoothing group. for i = 1 to 32 do ( if (bit.get impSmGrps i) == false do ( --Add smoothing group. polyOp.setFaceSmoothGroup obj faces (2 ^ (i - 1)) add:true; exit; ) ) for f in faces do ( hardSoftEdge.removeRedundantGroups obj f; ) ) ), -- --SET HARD EDGE -- function setHardEdge obj faces = ( --Get shared smoothing groups. local sharedSmGroups = (hardSoftEdge.getSharedSmGroups obj faces); --If there are shared smoothing groups, continue. Otherwise, edge is already hard. if sharedSmGroups != 0 do ( --Detect which edges will need resmoothing. local resmoothEdges = #(); for f in faces do ( --Get edges using face. local faceEdges = (polyOp.getEdgesUsingFace obj f); for e in faceEdges do ( --Get faces using edge. local edgeFaces = (polyOp.getFacesUsingEdge obj e); --Only use non-open edges. if edgeFaces.numberSet > 1 do ( for ef in edgeFaces do ( --Only look at faces that are not the faces to smooth initially. if faces[ef] == false do ( --Get face smoothing group. local efSmGroups = (polyOp.getFaceSmoothGroup obj ef); --Compare with smoothing groups that will be removed. --If there are shared smoothing groups between these faces, the edge --will need resmoothing. if (bit.and efSmGroups sharedSmGroups) != 0 do ( append resmoothEdges edgeFaces; ) ) ) ) ) ) --Remove shared smoothing groups. for f in faces do ( polyOp.setFaceSmoothGroup obj f (bit.xor (polyOp.getFaceSmoothGroup obj f) sharedSmGroups); ) --Resmooth surrounding edges. for e in resmoothEdges do ( hardSoftEdge.setSoftEdge obj e; ) for f in faces do ( hardSoftEdge.removeRedundantGroups obj f; ) ) ), -- --SET EDGE -- function setEdge mode = ( local obj = $; local objClass = (classOf obj); if (objClass == Editable_Poly) then ( --Get edge selection. local selEdges = (polyOp.getEdgeSelection obj); print selEdges.numberSet; print (polyOp.getNumEdges obj) if selEdges.numberSet == (polyOp.getNumEdges obj) then ( --All edges selected, so assign one smoothing group to all faces. if mode == "soft" then ( polyOp.setFaceSmoothGroup obj #{1..(polyOp.getNumFaces obj)} 1; ) else if mode == "hard" do ( polyOp.setFaceSmoothGroup obj #{1..(polyOp.getNumFaces obj)} 0; ) ) else ( for e in selEdges do ( --Get faces used by selected edges. local edgeFaces = (polyOp.getFacesUsingEdge obj e); --If not an open edge, continue. if edgeFaces.numberSet > 1 then ( if mode == "soft" then ( --Soft edge. hardSoftEdge.setSoftEdge obj edgeFaces; ) else if mode == "hard" do ( --Hard edge. hardSoftEdge.setHardEdge obj edgeFaces; ) ) else ( print ("Set " + mode + " edge warning: Open edge!"); ) ) ) ) else if (objClass == PolyMeshObject) then ( MessageBox "Sorry, Edit Poly modifiers are not supported." title:"Error"; ) else if (objClass == Editable_Mesh) then ( MessageBox "Editable Mesh is not supported.\nUse Editable Poly instead, it's better." title:"Error"; ) else ( print ("Set " + mode + " edge error: No Editable Poly selected!"); ) ) ) ---------------------------------------------------------------------- -----------------END OF HARD/SOFT EDGE--------------------- ---------------------------------------------------------------------- convertTo selection[1] PolyMeshObject modPanel.addModToSelection (Unwrap_UVW ()) ui:on local obj = selection[1]; local uv = obj.modifiers[ #unwrap_uvw ] ; uv.unwrap2.setTVSubObjectMode 2; ----------------------------------------------------------------------------------------------------- ----- Part of 'get open edges' function from TexTools' Roadkill script - by Renderhjs ------ ----------------------------------------------------------------------------------------------------- --get a list of all Shells filled with the Bitarary selections for each shell uv.selectFaces #{1..uv.unwrap.numberPolygons()}; uv.facetoedgeselect();--all edges are selected local allEdgesSelection = uv.unwrap2.getSelectedEdges(); local edgeElemArray = #(); for ed in allEdgesSelection do ( edgeElemArray[ ed ] = 0; ) local elem = #(); with redraw off; for ed in allEdgesSelection do ( if edgeElemArray[ ed ] == 0 then ( uv.unwrap2.selectEdges #{ ed }; uv.unwrap2.selectElement(); local elemEdges = uv.unwrap2.getSelectedEdges() as array; if elemEdges.count > 2 then (-- Ignore elements with less than 3 UV vertices. append elem (uv.unwrap2.getSelectedEdges()); for i in elemEdges do ( edgeElemArray[ i ] = elem.count; -- Mark these vertices with their element number in vertElemArray. ) ) ) ) --now get all the open Edges for all shells (elem = shell) local openEdgesSelection = #{};--the final open Edges selection for e in elem do( local selections = #(); local selectionsNum = #(); local maxNum = 0; for ed in e do( uv.unwrap2.selectEdges #{ ed }; uv.unwrap2.openEdgeSelect(); if ((uv.getselectededges()).numberset > 1)then( local sel = ((uv.unwrap2.getSelectedEdges()) - #{ ed });--substract the original edge, kinda a bug in the openEdgeSelect method openEdgesSelection+= sel;--add it to the bitarray (only not yet switched bits will be drawn) ) ) ) uv.unwrap2.selectEdges openEdgesSelection; local vertEdges = #(); if (openEdgesSelection.numberset > 0) then ( uv.unwrap2.setTVSubObjectMode 1;--otherwise Geo Verts are not recognized for e in openEdgesSelection do ( uv.unwrap2.selectEdges #{ e }; uv.edgeToVertSelect(); local vertList = uv.unwrap5.getSelectedGeomVerts(); if ( vertList.numberSet == 2 ) then ( append vertEdges (vertList as array); ) else ( exit;--error, some messy faces somewhere, where 1 edge connect with more than 2 geo verts ) ) --print("edges saved : "+openEdgesSelection.numberset as string+"x"); ) ------------------------------------------------------ ----- END OF Roadkill script - by Renderhjs ------ ------------------------------------------------------ --print vertEdges; --print "-----------" modPanel.addModToSelection (Edit_Poly ()) ui:on; local epoly = obj.modifiers[#Edit_Poly]; local edgeSelSet=#{} fn edge_from_verts v= ( ( polyOp.getEdgesUsingVert obj #{v[1]} ) - ( ( polyOp.getEdgesUsingVert obj #{v[1]} ) - ( polyOp.getEdgesUsingVert obj #{v[2]} ) ) ) for v = 1 to vertEdges.count do edgeSelSet += ( edge_from_verts vertEdges[v] ); modPanel.setCurrentObject epoly subobjectLevel = 2 epoly.Select #Edge edgeSelSet; --print edgeSelSet; maxOps.CollapseNodeTo obj 1 off hardSoftEdge.setEdge "hard"; )It's important to notice that script gives best results on hard surface models but for organic models this solution isn't a good idea - because of an undesired hard edges over the model. Use similar TexTools' function instead.
As I told above - this script was meant to be a part of XSI to Max exporter - to handle hard edges correctly.
Looking forward to your comments.
When I tried that script it made every edge a hard edge, any ideas?
not sure has been a long while since I wrote that. I never had my hands on 3dsMax 2012 or 2011 so I don't know if something is different in those versions. Maybe a wacky modifier stack that might not ideal for what this script does. Try it on a snapshot of your model - aka copy it and merge it down to just the base poly object. Otherwise is it possible to show a screen of the model, or modifier stack
It has some other cool things to
http://www.matthewlichy.com/turboTools.html
Basically I build a list of all the UV islands using a UV modifier, then set the auto smooth threshold to 180 and run auto smooth on each island.
I'll post the source here too incase my website ever goes down in the future.
------------------------------------- UV Islands To Smoothing Groups ------------------------------------- -- By Martin Palko -- [url]www.martinpalko.com[/url] -- -- -- Tested: - Max 2014 -- -- Install: - Drag script into max, then assign it to a hotkey or menu bar in "Customize -> User Interface" -- - Can be found by setting the category to "MP_Tools" while in the "Main UI" group -- -- Usage: - Select any mesh object, and activate the script. Choose a UV channel and hit run. -- -- Notes: - This script will not affect your modifier stack, it will simply put an edit poly with the -- smoothing groups at the top. -- - Undo is not currently supported, so save before, just in case. You can however, just delete -- the edit poly modifier to get your old smoothing groups back. -- - Selecting any channel other than UV channel 1 will show a dialogue box asking if you want to -- reset UVs. You must hit yes for it to work based on the channel you've specified. This -- will NOT affect the model's existing UV set. macroscript UVIslandsToSmoothing category:"MP_Tools" buttontext:"UV 2 Smooth" tooltip:"Convert UV islands to smoothing groups" autoUndoEnabled:false ( -- Helper function to get the index of the first true element in a bit array function getFirstActiveInBitarray aBitArray = ( for i = 1 to aBitArray.count do ( if aBitArray[i] == true do return i ) -- return 0 if none are found active return 0 ) -- Actually performs the operation on the currently selected object function ConvertUVIslandsToSmoothingGroups aUVChannel = ( if $ != undefined then ( modPanel.addModToSelection(Edit_Poly()) ui:on local editPoly = $.modifiers[#edit_poly] local facesDone = #{} -- empty bit array since no faces are done local allFaces = #{1.. polyop.getNumFaces $} local facesNotDone = allFaces -- Stick on a UVW modifier modPanel.addModToSelection (Unwrap_UVW ()) ui:on local uv_modifier = $.modifiers[#unwrap_uvw] uv_modifier.unwrap2.setTVSubObjectMode 3 -- Use face selection if (aUVChannel != 1) then -- Only need to mess with this if it's not default ( uv_modifier.unwrap.setMapChannel aUVChannel uv_modifier.unwrap.reset() forcecompleteredraw dodisabled:true -- Hacky fix for a bug, see [url]http://www.polycount.com/forum/showthread.php?t=97059[/url] ) local uv_islands = #() -- Empty array that will store bitarrays of all our UV islands local abort = false -- Abort boolean for breaking out of the loop and avoid the performance penalty of using break -- Build array of UV islands while (facesNotDone.isEmpty == false and abort == false) do ( nextFace = getFirstActiveInBitarray facesNotDone -- Get next face that hasn't been processed yet uv_modifier.unwrap2.selectFaces #{nextFace} -- Select that face uv_modifier.unwrap2.selectElement() -- Grow selection to element uv_island = uv_modifier.unwrap2.getSelectedFaces() -- Get a bitaray of all those faces (representing a UV island) -- Update faces done/not done bit masks facesDone += uv_island facesNotDone -= uv_island insertItem uv_island uv_islands (uv_islands.count + 1) -- Add that bitarray to our array of UV islands if uv_islands.count > allFaces.count then -- this should never happen, if it does means we are in an infinite loop and will crash max, so bail ( abort = true print ("Error, calculated too many islands, something went wrong") ) ) deletemodifier $ uv_modifier -- Don't need the UV modifier anymore editPoly.autoSmoothThreshold = 180.0 -- If we auto smooth, it should always be in the same smoothing group for island = 1 to uv_islands.count do -- Select and auto smooth each UV island ( editPoly.SetSelection #Face uv_islands[island] editPoly.ButtonOp #Autosmooth ) ) ) local isOpen = false -- Store if the rollout is open or closed rollout UV2SmoothRollout "UV_2_Smooth" ( spinner UVChannelSpinner "UV Channel" range:[1,99,1] type:#integer button GoBtn " Run " on GoBtn pressed do ( ConvertUVIslandsToSmoothingGroups (UVChannelSpinner.value) destroyDialog UV2SmoothRollout -- Close rollout after running ) on UV2SmoothRollout close do ( isOpen = false updateToolbarButtons() -- Update the toolbar icon when closing ) ) on execute do ( if isOpen then --if open, close it ( destroyDialog UV2SmoothRollout ) else --if closed, open it ( createDialog UV2SmoothRollout isOpen = true ) ) on isChecked return isOpen --return the flag on isEnabled do ( -- Need an editable poly selected to work if $ == undefined then ( -- Close the window if it's open and it shouldn't be if (isOpen) then destroyDialog UV2SmoothRollout return false ) else return true ) )