Notifications
Clear all

[Closed] recreating bad meshes imported from CAD

 em3

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

49 Replies
3 Replies
(@gazybara)
Joined: 11 months ago

Posts: 0

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
 )
 
 em3
(@em3)
Joined: 11 months ago

Posts: 0

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!

(@denist)
Joined: 11 months ago

Posts: 0

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)
 em3

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

1 Reply
 em3
(@em3)
Joined: 11 months ago

Posts: 0

Thank you gazybara, I’ll try it!

 em3

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"
	

2 Replies
(@gazybara)
Joined: 11 months ago

Posts: 0

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 ?

(@plastic)
Joined: 11 months ago

Posts: 0

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

1 Reply
(@gazybara)
Joined: 11 months ago

Posts: 0

Here is max2010 x64 version

Here is max2010 x64 version

these meshes are all broken. probably it’s a result of bad import. how do you import these meshes?

3 Replies
(@plastic)
Joined: 11 months ago

Posts: 0

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.

(@denist)
Joined: 11 months ago

Posts: 0

is any way to export AutoCAD meshes in some ASCII format? (or OBJ)

(@gazybara)
Joined: 11 months ago

Posts: 0

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.

1 Reply
(@plastic)
Joined: 11 months ago

Posts: 0

very nice!

Page 1 / 3