[Closed] recreating bad meshes imported from CAD
Hi! In my never ending quest to make right the wrongs that are cast onto my CAD meshes once they have been imported into 3DS Max, I have decided that perhaps I should try to redraw the objects with maxscript.
foo = $
theface = for f = 1 to foo.numfaces collect getFace foo f
vertarr = for a = 1 to foo.numverts collect getVert foo a
(
themesh = mesh vertices:vertarr faces:theface
c = convertToPoly(themesh)
polyop.setFaceSelection c #all
c.autoSmooth()
centerpivot c
)
This actually works great! Sadly though, it doesn’t fix the problems I am having with inverted normals. I thought that by recreating new verts/faces that max would orient the normals the correct way, I was wrong! I ran some tests with and without normal modifiers on the objects I was tracing and it looks like the face array and vertex array have the same values for each.
Where is max getting the vertex normal vector from and how can I tell it that all normals should be pointed out, never in. Thanks!
edit:
--with normal modifier
OK
$Editable_Mesh:equip_boiler_bldg_Level 3 @ [29934.023438,40654.171875,375.708313]
#([2,1,3], [4,1,2], [4,2,5], [7,6,8], [6,7,9], [11,10,12], [10,11,13], [15,14,16], [14,15,17], [19,18,20], [18,19,21], [23,22,24], [22,23,25], [27,26,28], [29,3,26], [30,29,26], [27,30,26], [25,27,28], [25,28,22], [21,23,24], ...)
#([29931,40655.5,380], [29931,40655.5,373.25], [29930.7,40654.2,380], [29931.8,40656.6,380], [29931.8,40656.6,373.25], [29934.4,40657.5,380], [29933,40657.3,373.25], [29933,40657.3,380], [29934.4,40657.5,373.25], [29936.8,40656.1,380], [29935.7,40657.1,373.25], [29935.7,40657.1,380], [29936.8,40656.1,373.25], [29937.3,40653.5,380], [29937.3,40654.9,373.25], [29937.3,40654.9,380], [29937.3,40653.5,373.25], [29935.7,40651.3,380], [29936.8,40652.2,373.25], [29936.8,40652.2,380], ...)
true
OK
--without normal modifier
$Editable_Mesh:equip_boiler_bldg_Level 3 @ [29934.023438,40664.339844,375.708313]
#([1,2,3], [1,4,2], [2,4,5], [6,7,8], [7,6,9], [10,11,12], [11,10,13], [14,15,16], [15,14,17], [18,19,20], [19,18,21], [22,23,24], [23,22,25], [26,27,28], [3,29,26], [29,30,26], [30,27,26], [27,25,28], [28,25,22], [23,21,24], ...)
#([29931,40665.7,380], [29931,40665.7,373.25], [29930.7,40664.3,380], [29931.8,40666.8,380], [29931.8,40666.8,373.25], [29934.4,40667.7,380], [29933,40667.5,373.25], [29933,40667.5,380], [29934.4,40667.7,373.25], [29936.8,40666.3,380], [29935.7,40667.2,373.25], [29935.7,40667.2,380], [29936.8,40666.3,373.25], [29937.3,40663.6,380], [29937.3,40665,373.25], [29937.3,40665,380], [29937.3,40663.6,373.25], [29935.7,40661.5,380], [29936.8,40662.4,373.25], [29936.8,40662.4,380], ...)
true
OK
I modified a bit
foo = $
if not isKindOf foo Editable_mesh do convertToMesh foo
theface = for f = 1 to foo.numfaces collect getFace foo f
vertarr = for a = 1 to foo.numverts collect getVert foo a
(
themesh = mesh vertices:vertarr faces:theface
meshop.autoEdge themesh #{1..(themesh.Edges.count)} 24 type:#SetClear
faces = #{1..themesh.numfaces}
meshop.unifyNormals themesh faces
for i = 1 to 2 do meshop.flipNormals themesh faces
--meshop.autoSmooth themesh faces 0.0
--update themesh
CenterPivot themesh
ResetXForm themesh
newobj = converttopoly themesh
newobj.selectedfaces = #{1..newobj.numfaces}
newobj.setSmoothingGroups 0 -1 1
select newobject
)
Worked great on my first test. Sadly, it falls over on this scene https://dl.dropbox.com/u/1292183/weird%20pipe.rar
My routine will redraw it, but it still comes out wrong. I will keep plugging away and let you know what I find.
Thanks!
could you attach some sample max file with mesh (the result of your conversion) with broken normals.
i need a subject to play with.
max probably generates the normal from vertices in the face, and direction is dependent on the order of the verts in the face,
#(1,2,3) vs #(3,2,1)
try…
fn flipface f = ( [f.z,f.y,f.x] )
theface = for f = 1 to foo.numfaces collect flipface (getFace foo f)
Thank you for your responses claude666 and gazybara!
gazybara, your method is so much faster than what I had hacked together by the wee morning hours! Thank you!
This is what I had. About 500x slower.
function collectFlippedFaces theNode selectIt:true =
(
local baFlippedFaces = #{}
if (isKindOf theNode Editable_Poly) do
(
local p3ViewDir = -(inverse(getViewTM())).row3
local iNumFaces = polyOp.getNumFaces theNode
for iFace = 1 to iNumFaces where ( (dot (polyOp.getFaceNormal theNode iFace) p3ViewDir) > 0 ) do(
baFlippedFaces[iFace] = true
)
)
return baFlippedFaces
)
fn redrawmesh foo=(
local thergb = for x = 1 to 3 collect (random 1 255)
local theface = for f = 1 to foo.numfaces collect getFace foo f
local vertarr = for a = 1 to foo.numverts collect getVert foo a
local themesh = mesh vertices:vertarr faces:theface
local c = convertToPoly(themesh)
local fs = polyop.setFaceSelection c #all
local sm = c.autoSmooth()
local cp = centerpivot c
themesh.wirecolor = color thergb[1] thergb[2] thergb[3]
select themesh
return themesh
)
disablesceneredraw()
thedelarr = #()
flipit = #()
(
local objs = for obj in selection collect obj
for obj in objs do
(
if (isKindOf obj Editable_Poly) == true then converttomesh obj
--append thedelarr obj
local o = redrawmesh obj
append thedelarr obj
local fc = collectFlippedFaces o
if fc.count > 1 then(
if (subObjectLevel != 4) then subObjectLevel = 4
p = PolyOp.getElementsUsingFace o fc
polyOp.setFaceSelection o p
polyop.flipNormals o p
subObjectLevel=0
forceCompleteRedraw()
)
)
delete thedelarr
)
enablesceneredraw()
You can speed up the code if you use polyOp methods (interface fn) outside the “for-loop”.
Store it (polyOp fn) in variables. Not need to call theses fn’s over and over again. It is slow
getF_Norm = polyOp.getFaceNormal
setF_Sel = polyop.setFaceSelection
-- or i think fester solution is
c.selectedfaces = #{1..c.numfaces} -- select all faces
getEleUF = PolyOp.getElementsUsingFace
flipF = polyop.flipNormals
Denis, here is a small part of a file https://dl.dropbox.com/u/1292183/weird-pipe-max-2013.rar
There are far larger files crammed with lots of “stuff” but this small example illustrates the issue I am having.
Thanks dudes!
I hacked together a script that I use daily for that purpose.
It works great for imported 2d autocad meshes (hatch solids). They always come with randomly flipped normals, so this script aligns every normal to the z axis of the top viewort. It works 100% reliable, as opposed to “unify normals”, which never seems to work.
It needs to run in the top viewport (I guess that could be easily fixed, but it’s all right for my purpose)
Credits for the functions go to:
http://www.illusioncatalyst.com/mxs.php
--unifies all normals for selected meshes in the top viewport
function getPolyFromMeshFace oPoly iMeshFace =
(
if ( (not isValidNode oPoly) or ((classOf oPoly) != Editable_Poly) and ((classOf iMeshFace) != Integer) ) then
(
return 0
)
if ( (iMeshFace <= 0) or (iMeshFace > (meshOp.getNumFaces oPoly.mesh)) ) then
(
return 0
)
local baMeshVert = meshOp.getVertsUsingFace oPoly.mesh iMeshFace
local aiPolyFromVert = #()
for iVert in baMeshVert do
(
append aiPolyFromVert (polyOp.getFacesUsingVert oPoly iVert)
)
local baPolyFace = aiPolyFromVert[1] * aiPolyFromVert[2] * aiPolyFromVert[3]
if (baPolyFace.numberSet == 1) then
(
return (baPolyFace as Array)[1]
)
else
(
local baMeshFaceInPoly = meshOp.getPolysUsingFace oPoly.mesh iMeshFace ignoreVisEdges:false threshhold:179.5
local baMeshFaceVert = meshOp.getVertsUsingFace oPoly.mesh baMeshFaceInPoly
local baPolyFaceVert = #{}
for iFace in baPolyFace do
(
baPolyFaceVert = polyOp.getVertsUsingFace oPoly iFace
if ( ((baPolyFaceVert - baMeshFaceVert).isEmpty == true) and \
((baMeshFaceVert - baPolyFaceVert).isEmpty == true) ) then
(
return iFace
)
)
)
)
--------------------------------------------------------------------------------
function posMatch pA pB fT =
(
if ((classOf pA == Point3) and (classOf pB == Point3) and (classOf fT == Float)) then
(
return (((abs(pA.x - pB.x)) < fT) and ((abs(pA.y - pB.y)) < fT) and ((abs(pA.z - pB.z)) < fT))
)
else
(
throw "Wrong input in function posMatch()"
)
)
--------------------------------------------------------------------------------
function getFrontFaces obj steps:0 =
(
if ((classOf steps) != Integer) then
(
throw "Wrong input in function getFrontFaces(): steps must be a positive Integer"
)
local fDotThresh = 1e-3
local fMatchThresh = 1e-3
local fOffsetThresh = 1e-2
local baFrontFaces = #{}
local iNumFaces = 0
local p3FaceCenter = [0,0,0]
local p3FaceNormal = [0,0,0]
local p3CameraToFaceVect = [0,0,0]
local fDotViewAngle = 0.0
local p3CameraPos = (inverse(getViewTM())).row4
local p3CameraViewDir = -(inverse(getViewTM())).row3
local bIsPerspective = viewport.isPerspView()
local fObjMaxDim = distance obj.max obj.min
-- Editable Poly ---------------------------------------------------------------
if ((classOf obj) == Editable_Poly) then
(
iNumFaces = polyOp.getNumFaces obj
for i = 1 to iNumFaces do
(
p3FaceCenter = polyOp.getFaceCenter obj i
p3FaceNormal = polyOp.getFaceNormal obj i
if (bIsPerspective == true) then
(
p3CameraToFaceVect = p3FaceCenter - p3CameraPos
fDotViewAngle = dot p3CameraToFaceVect p3FaceNormal
)
else
(
fDotViewAngle = dot p3CameraViewDir p3FaceNormal
)
if (fDotViewAngle < -fDotThresh) then ( baFrontFaces[i] = true )
)
-- set test vert ---------------------------------------------------------------
local iNumTestVerts = polyOp.getNumVerts obj
local ap3TestVertPos = for i = 1 to iNumTestVerts collect (polyOp.getVert obj i)
if (steps > 0) then
(
local iNumAllEdges = polyOp.getNumEdges obj
local aiEdgeVerts = #()
local ap3NewTestPos = #()
local p3EdgeVector = [0,0,0]
for i = 1 to iNumAllEdges do
(
aiEdgeVerts = polyOp.getEdgeVerts obj i
for j = 1 to steps do
(
p3EdgeVector = ap3TestVertPos[aiEdgeVerts[2]] - ap3TestVertPos[aiEdgeVerts[1]]
append ap3NewTestPos (ap3TestVertPos[aiEdgeVerts[1]] + ((p3EdgeVector / (steps +1)) * j) )
)
)
join ap3TestVertPos ap3NewTestPos
iNumTestVerts = ap3TestVertPos.count
)
-- run test --------------------------------------------------------------------
local ap3CameraToVertDir = #()
local rTest = ray [0,0,0] [0,0,0]
local rResult = ray [0,0,0] [0,0,0]
local aResult = #()
local p3RayPos = [0,0,0]
local aIterator = #(true)
with redraw off
(
local oMesh = snapShot obj
-- ray from vert away from camera ----------------------------------------------
for i = 1 to iNumTestVerts do
(
if (bIsPerspective == true) then
ap3CameraToVertDir[i] = normalize(ap3TestVertPos[i] - p3CameraPos)
else
ap3CameraToVertDir[i] = p3CameraViewDir
p3RayPos = ap3TestVertPos[i] + ap3CameraToVertDir[i] * fOffsetThresh
aIterator = #(0)
for dummy in aIterator do
(
rTest = ray p3RayPos ap3CameraToVertDir[i]
aResult = intersectRayEx oMesh rTest
if (aResult != undefined) then
(
baFrontFaces -= #{getPolyFromMeshFace obj aResult[2]}
p3RayPos = aResult[1].pos + ap3CameraToVertDir[i] * fOffsetThresh
append aIterator 0
)
)
)
baFrontVerts = polyOp.getVertsUsingFace obj baFrontFaces
-- ray from camera to vert -----------------------------------------------------
for iVert in baFrontVerts do
(
if (bIsPerspective == true) then
(
rTest = ray p3CameraPos ap3CameraToVertDir[iVert]
)
else
(
p3RayPos = ap3TestVertPos[iVert] - p3CameraViewDir * fObjMaxDim
rTest = ray p3RayPos p3CameraViewDir
)
aResult = intersectRayEx oMesh rTest
if (aResult != undefined) then
(
if ((posMatch ap3TestVertPos[iVert] aResult[1].pos fMatchThresh) == false) then
(
if (distance p3CameraPos aResult[1].pos) < (distance p3CameraPos ap3TestVertPos[iVert]) then
(
baFrontFaces -= polyOp.getFacesUsingVert obj iVert
)
)
)
)
baFrontVerts = polyOp.getVertsUsingFace obj baFrontFaces
-- ray from vert to camera -----------------------------------------------------
for iVert in baFrontVerts do
(
if (bIsPerspective == true) then
(
p3RayPos = ap3TestVertPos[iVert] - ap3CameraToVertDir[iVert] * fOffsetThresh
rTest = ray p3RayPos -ap3CameraToVertDir[iVert]
)
else
(
p3RayPos = ap3TestVertPos[iVert] - p3CameraViewDir * fOffsetThresh
rTest = ray p3RayPos -p3CameraViewDir
)
rResult = intersectRay obj rTest
if (rResult != undefined) then
(
baFrontFaces -= polyOp.getFacesUsingVert obj iVert
)
)
delete oMesh
)
polyOp.setFaceSelection obj baFrontFaces -- just for visual feedback, remove if not needed
)
-- Editable Mesh ---------------------------------------------------------------
else if ((classOf obj) == Editable_Mesh) then
(
iNumFaces = meshOp.getNumFaces obj
for i = 1 to iNumFaces do
(
p3FaceCenter = meshOp.getFaceCenter obj i
p3FaceNormal = getFaceNormal obj i
if (bIsPerspective == true) then
(
p3CameraToFaceVect = p3FaceCenter - p3CameraPos
fDotViewAngle = dot p3CameraToFaceVect p3FaceNormal
)
else
(
fDotViewAngle = dot p3CameraViewDir p3FaceNormal
)
if (fDotViewAngle < -fDotThresh) then ( baFrontFaces[i] = true )
)
-- set test vert ---------------------------------------------------------------
local iNumTestVerts = meshOp.getNumVerts obj
local ap3TestVertPos = for i = 1 to iNumTestVerts collect (meshOp.getVert obj i)
if (steps > 0) then
(
with redraw off
(
local oPoly = snapShot obj
local iNumAllFaces = meshOp.getNumFaces oPoly
for i = 1 to iNumAllFaces do
for j = 1 to 3 do
setEdgeVis oPoly i j true
oPoly = convertToPoly oPoly
local iNumAllEdges = polyOp.getNumEdges oPoly
local aiEdgeVerts = #()
local ap3NewTestPos = #()
local p3EdgeVector = [0,0,0]
for i = 1 to iNumAllEdges do
(
aiEdgeVerts = polyOp.getEdgeVerts oPoly i
for j = 1 to steps do
(
p3EdgeVector = ap3TestVertPos[aiEdgeVerts[2]] - ap3TestVertPos[aiEdgeVerts[1]]
append ap3NewTestPos (ap3TestVertPos[aiEdgeVerts[1]] + ((p3EdgeVector / (steps +1)) * j) )
)
)
join ap3TestVertPos ap3NewTestPos
iNumTestVerts = ap3TestVertPos.count
delete oPoly
)
)
-- run test --------------------------------------------------------------------
local ap3CameraToVertDir = #()
local rTest = ray [0,0,0] [0,0,0]
local rResult = ray [0,0,0] [0,0,0]
local aResult = #()
local p3RayPos = [0,0,0]
local aIterator = #(true)
-- ray from vert away from camera ----------------------------------------------
for i = 1 to iNumTestVerts do
(
if (bIsPerspective == true) then
ap3CameraToVertDir[i] = normalize(ap3TestVertPos[i] - p3CameraPos)
else
ap3CameraToVertDir[i] = p3CameraViewDir
p3RayPos = ap3TestVertPos[i] + ap3CameraToVertDir[i] * fOffsetThresh
aIterator = #(0)
for dummy in aIterator do
(
rTest = ray p3RayPos ap3CameraToVertDir[i]
aResult = intersectRayEx obj rTest
if (aResult != undefined) then
(
baFrontFaces[aResult[2]] = false
p3RayPos = aResult[1].pos + ap3CameraToVertDir[i] * fOffsetThresh
append aIterator 0
)
)
)
baFrontVerts = meshOp.getVertsUsingFace obj baFrontFaces
-- ray from camera to vert -----------------------------------------------------
for iVert in baFrontVerts do
(
if (bIsPerspective == true) then
(
rTest = ray p3CameraPos ap3CameraToVertDir[iVert]
)
else
(
p3RayPos = ap3TestVertPos[iVert] - p3CameraViewDir * fObjMaxDim
rTest = ray p3RayPos p3CameraViewDir
)
aResult = intersectRayEx obj rTest
if (aResult != undefined) then
(
if ((posMatch ap3TestVertPos[iVert] aResult[1].pos fMatchThresh) == false) then
(
if (distance p3CameraPos aResult[1].pos) < (distance p3CameraPos ap3TestVertPos[iVert]) then
(
baFrontFaces -= meshOp.getFacesUsingVert obj iVert
)
)
)
)
baFrontVerts = meshOp.getVertsUsingFace obj baFrontFaces
-- ray from vert to camera -----------------------------------------------------
for iVert in baFrontVerts do
(
if (bIsPerspective == true) then
(
p3RayPos = ap3TestVertPos[iVert] - ap3CameraToVertDir[iVert] * fOffsetThresh
rTest = ray p3RayPos -ap3CameraToVertDir[iVert]
)
else
(
p3RayPos = ap3TestVertPos[iVert] - p3CameraViewDir * fOffsetThresh
rTest = ray p3RayPos -p3CameraViewDir
)
rResult = intersectRay obj rTest
if (rResult != undefined) then
(
baFrontFaces -= meshOp.getFacesUsingVert obj iVert
)
)
setFaceSelection obj baFrontFaces -- just for visual feedback, remove if not needed
)
-- Neither Editable Poly, nor Editable Mesh ------------------------------------
else
(
throw "Wrong input in function getFrontFaces()"
)
return baFrontFaces
)
currentView=viewport.getType()
if (currentView == #view_top) then
(
undo "fix normals" on
(
local obj_count=0
local frontFaces_count=0
local totalFaces_count=0
local totalNodesProcessed=selection.count
--disableSceneRedraw()
progressStart "fixing faces..."
local percent_count=1
--unify faces to current view
for obj in selection where (classOf obj==Editable_Mesh) do
(
obj_count += 1
frontFaces = getFrontFaces obj
frontFaces_count += frontFaces.numberSet
totalFaces_count += obj.numfaces
meshop.flipNormals obj frontFaces
meshop.flipNormals obj #{1..obj.numfaces}
setFaceSelection obj #{}
update obj
progressUpdate ((obj_count as float) / totalNodesProcessed * 100)
)
progressEnd()
--enableSceneRedraw()
local flippedFaces_count = (totalFaces_count - frontFaces_count)
messageBox ((obj_count as string) + " mesh objects processed.
" + (totalFaces_count as string) + " faces.
" + (flippedFaces_count as string) + " faces fixed.") title:"Info" )
)
else messageBox "Can only operate in Top viewport, since the normals get aligned to the current view. Please chance active viewport and try again." title:"Error"
Very cool.
You are right for “unify normals” fn. It’s not reliable at all.
Whay do you need to use TopView when you can use TM = matrix3 1 ?
Yeah, sounds better
I attached an example of typical imported CAD walls. When extruded with the shell modifier they go up and down. When fixed with the script I posted before, the extrusion is fine. UnifyNormals doesn’t help at all in this case.
Original max2010 file attached…
Note that the reason why I don’t use CAD spline outlines for extrusion is because they are even more messed up to a point which makes them unuseable. Double segments, broken verts, etc.
i can’t load this file. it was save in 2013. i have the 2012 highest. please save it as 2010 or 2012
The thing is, they are broken by design somehow. If you export a mesh from programs like AutoCAD, ArchiCAD, what’s coming out is a mess, regardless how you import it.
Yep. I use arc’s in CAD and modify them over the splines. And when i have to use spline in some extreme cases then i drop them in separate layer and in max i need to use this fn every
time which is boring.
But in most cases i recive CAD files from other people and ther is a mess
fn reduceSplinePoints spl knots: collapseSpl:on =
(
local splLength = curvelength spl
addModifier spl (Normalize_Spl length:(splLength/dots))
if collapseSpl do convertToSplineShape spl
)
plastic,
in your case everything is easy to fix:
fn flipAllFacesNonUp node = if iskindof node Editable_Mesh do
(
up = [0,0,1]
ff = #{}
for f=1 to node.numfaces where (dot up (getfacenormal node f)) < 0 do append ff f
node.selectedfaces = ff
meshop.flipnormals node ff
update node
)
/*
flipAllFacesNonUp $'Color:008'
*/
to select faces is not necessary. i do it just to show which were flipped by the function.
but it’s not a good import either.