Notifications
Clear all

[Closed] Help with cutting hole in 2D mesh polygon

Hi!

I need some help / advice on cutting holes in 2D mesh polygons, where the hole verts/edges is known and is on the same plane as the 2D mesh.

I got a simpleObject plugin that is creating a mesh from Truetype text. I detect “hole” figures/shapes with a brute force method that checks if shape startpoint is inside a previously defined polygon. The attached plugin require .NET Framework 3.0

The attached sample code is not optimized, lacks proper error handling, may contain memory-leaks, it lacks support for curve-interpration and may need some other work on, but it should work as a demonstration for my problem. I use 3dsmax 9 SP2

I am now just ignoring the holes and dismisses them with a meshop.deleteIsoVerts command.
What I need help with is help/advises/suggestions or ideas on how to cut holes in one polygon using another polygon (or vert positions). For example the letter “O” is 2 figures, I need to “boolean” out the inner polygon from the outer one.

I have explicit reasons for using simpleOBject, and other reasons for not using Shape-nodes or 3dsmax builtin Text objects. I also have reasons for not using builtin Boolean operations. That said, I need help on creating my own cutting algorithm.

My intention is not to cut the mesh after it is extruded, but cutting it when creating the 2D mesh

I have one idea:

  1. Iterate “hole”-verts, check what face the vert is inside, divide that face
  2. Iterate “hole”-verts again, cutting edges where no edge is between the verts
  3. Somehow select all faces inside “hole” edges, and delete the faces.

But I am not sure how I will execute that properly, of if it works in all shapes.
Any help is appreciated, thanks in advance! // Peter



/*

	TTF to mesh
	version 0.1
	Last Edited: 26/03/2010

	by Peter Larsen 2010
	
	This plugin requires .Net framework 3.0.
	Tested using 3dsmax v9 SP2

	This sourcecode is Free to use in any way you may want to. Use this at your own risk.
	
	History:	v0.1	This version lacks feature to cut holes in the mesh shapes.
						Code is not optimized and poor error handling routines.
						Fontlist collector is not dotnet, but regedit hack.
						
	Todo:				Cut holes in geometry instead of ignoring those shapes.
						Optimized code, better error handling.
						Collecting fonts using dotnet classes.
						Interpolating curves.

*/

plugin simpleObject LarsenTtfToMesh_plugin_def

name:"Ttf To Mesh"
classID:#(666666,888888)
category:"Scripted Primitives"
( 
	local ttfonts = #()	
	
	local ttfverts = #()
	local ttffaces = #()
	
	local createFontList, ttftomesh, updateText
	
	parameters ttftmeshParams rollout:ROttftmesh
	(
		TTFToMesh_height type:#worldUnits ui:TTFToMesh_height 	default:0	animatable:true
		TTFToMesh_width type:#worldUnits ui:TTFToMesh_width 	default:0 animatable:true
		TTFToMesh_depth type:#worldUnits ui:TTFToMesh_depth		default:0 animatable:true
		
		selfont  type:#string ui:lbttFonts 			animatable:true
		seltext  type:#string ui:ttfText 			default:"Test" animatable:true

	)
	
	rollout params "About" rolledUp:true
	(
	      label about1 "\"TTF to mesh\""
		  label about2 "v0.1"
		  label about3 "by Peter Larsen 2010"
	)
	
	rollout ROttftmesh "Main Parameters" rolledUp:false
	(
		spinner TTFToMesh_height	"Height" 	type:#worldunits 	range:[-1000,1000,0]
		spinner TTFToMesh_width 	"Width" 	type:#worldunits 	range:[-1000,1000,0]
		spinner TTFToMesh_depth		"Depth" 	type:#worldunits 	range:[-1000,1000,0]
	
		group "Text and font"
		(
			dropdownlist lbttFonts "Font"
			edittext ttfText "Text :" fieldWidth:100 labelOnTop:true text: "Test"
		)
		
		on ttfText changed newStr do
		(
		
				updateText()
		) -- on ttfText changed state
		
		on lbttFonts selected sel do
		(
		
				updateText()
		) -- on ttfText changed state


		on ROttftmesh open do
		(
			setWaitCursor()
			
			-- fontlist
			if ttfonts.count == 0 do
					createFontList ttfonts
		
			if ttfonts.count != 0 do
				if lbttFonts.items.count == 0 do
					lbttFonts.items = ttfonts
					
			updateText()			
			setArrowCursor()

		) -- on ROttftmesh open
	) -- rollout ROttftmesh
	
	
	on buildMesh do
	(
		disableSceneRedraw() 
    	suspendEditing() 
		
		local vertsBx = #()
	
		undo off
		(
			if autosave.Enable do autosave.resettimer() -- reset AS timer (just for safe)
		
			setWaitCursor()
			local timeStart = timestamp()

			for v = 1 to ttfverts.count do
			(			
				-- set size
				vertsBx [v] = [	ttfverts[v][1] * TTFToMesh_width,
								ttfverts[v][2] * TTFToMesh_depth,
								ttfverts[v][3] * TTFToMesh_height  ]
			)	

			-- create mesh
			setMesh mesh \
				verts: vertsBx  \
				faces: ttffaces

			meshop.extrudeFaces mesh #{1..(ttffaces.count)} (TTFToMesh_height) 0 dir:#common 

			update mesh

			local timeEnd = timestamp()
			setArrowCursor()
			format "
Total Time:		% sec.
" ((timeEnd - timeStart)/1000.0)

		)
		
		resumeEditing() 
 		enableSceneRedraw()
		redrawviews()
		
		gc()
	)
	
	tool create
	(
		on mousePoint click do
		(
			case click of
			(
				1: nodeTM.translation = gridPoint
				3: #stop
			)
		) -- on mousePoint click
	
		on mouseMove click do
		(		
			case click of
			(
				2: (TTFToMesh_width = gridDist.x; TTFToMesh_depth = gridDist.y)
				3: TTFToMesh_height = gridDist.z
			)
		) -- on mouseMove click
	) -- tool create


	function updateText =
	(
		-- get font and text
		sel = ROttftmesh.lbttFonts.selection
		if(sel >0) do
		(
			selfont = ROttftmesh.lbttFonts.items[sel]
			seltext = ROttftmesh.ttfText.text
			
			ttfverts = #()
			ttffaces = #()
			
			if (seltext.count > 0) do
				ttftomesh ttfverts ttffaces selfont seltext
		)
	
	
	)

	fn existFile fname = (getfiles fname).count != 0

	function createFontList ttfonts =
	(
			-- directories
			tmppath= (Getdir #temp ) + "\	tfonts\	ruetype.txt"
			tmppath2= (Getdir #temp ) + "\	tfonts\\ascii_tt.txt"
	
			-- Check
			if ((existFile tmppath) == false) do
			(
				makeDir ((Getdir #temp) + "\	tfonts")
				cmdstr = "regedit /e \"" + tmppath +  "\" \"HKEY_LOCAL_MACHINE\software\Microsoft\Windows NT\CurrentVersion\Fonts\""
				DOSCommand cmdstr
				
				-- Unicode to ascii
				cmdstr = "type \"" + tmppath +  "\" > \"" + tmppath2 +  "\""
				DOSCommand cmdstr
			)
			
			if ttfonts.count == 0 do
			(
				-- load ascii_tt.txt
				ttffs = openFile tmppath2
				
				if(ttffs != undefined) then
				(
					tmpStr = ""
					oldtmpStr = ""
					while not eof ttffs do
					(
						oldtmpStr = copy tmpStr
						tmpStr = readDelimitedString ttffs "\""
						
						if(tmpStr[1] == "=") do
						(
							tmpfname_arr = filterString oldtmpStr "("
							tmpfnametr = trimRight tmpfname_arr[1]
							append ttfonts (copy tmpfnametr)
						)
					)
					
					sort ttfonts 		
					close ttffs
				)		
			)
	) -- function createFontList 


	fn isInside obj testpoint =
	(
		local sum = 0
		local f = 1
		
		numFaces = meshop.getNumFaces obj
		
		-- loop all faces, if 360 then point is inside face
		while ( (sum < 350) and (f <= numFaces) ) do
		(
			-- Get 3 edges
			local faceEdges = (meshop.getEdgesUsingFace obj f) 
			faceEdges = faceEdges as array
		
			sum = 0
			
			-- Loop 3 edges
			for i = 1 to faceEdges.count do
			(
				-- Get edge verts
				local edgeVerts = meshop.getVertsUsingEdge obj faceEdges[i]
				edgeVerts = edgeVerts as array
				
				-- Get verts position and normalize vectors to testpoint
				local n1 = normalize ( (meshop.getVert obj edgeVerts[1]) - testpoint)
				local n2 = normalize ( (meshop.getVert obj edgeVerts[2]) - testpoint)

				-- sum vectors
				sum += acos (dot n1 n2)
			)

			f += 1		
		)
		
		-- if 360 then then point is inside
		if(sum>=350) then
			return true
		else
			return false
	)
	
	
	fn getSeg verts w h figSeg curSp knoti corner =
	(
		-- Get Point
		segPoint = figSeg.Point
		
		append verts  ([segPoint.x/w, segPoint.y/h, 0])
		
	) -- fn getSeg
	
	fn getPolySeg verts w h figSeg curSp knoti line corner =
	(
		-- Get Point Collection
		segPointCollection = figSeg.Points
		points = segPointCollection.CloneCurrentValue()
	
		-- Count, Enumerator
		pointCount 	= points.count
		pointEnum 	= points.GetEnumerator()
	
		for iter_points = 1 to pointCount do
		(
			-- Get current Point
			pointEnum.MoveNext()
			segPoint = pointEnum.Current
				
			append verts  ([segPoint.x/w, segPoint.y/h, 0])
			
		) -- iter_points
	) -- fn getPolySeg


	function ttftomesh newverts newfaces sfont stext=
	(
		-- loading dotnet assemblies
		dotnet.loadassembly "PresentationCore"
		dotnet.loadassembly "System"
		
		-- classes
		geoClass = dotNetClass "System.Windows.Media.PathGeometry"
		
		-- Init FormattedText arguments
		txClass = dotNetClass "System.String"
		txObj	= dotNetObject txClass stext
		
		fsClass = dotNetClass "System.Windows.FontStyle"
		fsObj 	= dotNetObject fsClass (dotNetClass "FontStyles.Normal")
		
		fwClass = dotNetClass "System.Windows.FontWeight"
		fwObj 	= dotNetObject fwClass (dotNetClass "FontWeights.Medium")
		
		ciClass = dotNetClass "System.Globalization.CultureInfo"
		ciObj	= dotNetObject ciClass "en-us"
		ciObj2	= ciObj.GetCultureInfo "en-us"
		
		fdClass	= dotNetClass "System.Windows.FlowDirection"
		fdObj	= fdClass.LeftToRight
		
		tfClass	= dotNetClass "System.Windows.Media.Typeface"
		tfObj	= dotNetObject tfClass sfont
		
		siObj = dotNetObject "System.Double" 32
		
		brClass = dotNetClass "System.Windows.Media.SolidColorBrush"
		brObj	= dotNetObject brClass 
		
		ftClass = dotNetClass "System.Windows.Media.FormattedText"
		
		-- Create FormattedText instance
		ftObj 	= dotNetObject ftClass txObj ciOBj2 fdObj tfObj siObj brObj
		
		-- Position Point class and Object
		ptClass = dotNetClass "System.Windows.Point"
		ptObj	= dotNetObject ptClass 0 0
		
		-- Call BuildGeometry Method, returns Geometry
		geoObj	= ftObj.BuildGeometry ptObj
		
		-- Get Bounding Bounds
		boundsRect = getProperty geoObj #Bounds asDotNetObject:true
		
		-- Get width and height from Rect
		w = getProperty boundsRect #Width
		h = getProperty boundsRect #Height
		
		-- Get path
		geoPath = geoObj.GetOutlinedPathGeometry()
		
		-- Get figures
		geoPathClone = geoPath.CloneCurrentValue()
	
		geoFigures = getProperty geoPathClone #Figures asDotNetObject:true
		geoFillrule = getProperty geoPathClone #Fillrule asDotNetObject:true

		-- Get Figures count
		geoFigCount = geoFigures.Count
		
		-- Get Figure Item
		figEnum = geoFigures.GetEnumerator()
			
		-- Variables
		verts = #()
		polys = #()
	
		for iter_figs = 1 to geoFigCount do
		(
			figEnum.MoveNext()
			geoFig0 = figEnum.Current
		
			-- Get a Figure startpoint "System.Windows.Point"
			geoStartPoint	= geoFig0.startpoint
			-- isclosed
			figIsClosed 	= geoFig0.IsClosed
			-- isFilled
			figIsFilled 	= geoFig0.IsFilled
	
			append verts ([geoStartPoint.x / w, geoStartPoint.y / h, 0])
			append polys verts.count
			
			-- Get Segments Collection
			geoPathFigureSegCollection = geoFig0.Segments
			segs = geoPathFigureSegCollection.CloneCurrentValue()
			
			segsCount = segs.count
			segsEnum = segs.GetEnumerator()
			for iter_segs = 1 to segsCount do
			(
				-- Get current Segment
				segsEnum.MoveNext()
				figSeg = segsEnum.Current
				
				-- Get Segment Type
				segStr = figSeg.ToString()
				
				-- IsSmoothJoin
				IsSmoothJoin 	= figSeg.IsSmoothJoin
				-- IsStroked
				IsStroked		= figSeg.IsStroked
								
				if (segStr == "System.Windows.Media.LineSegment") do (getSeg verts w h figSeg curSp knoti IsSmoothJoin)
				if (segStr == "System.Windows.Media.PolyLineSegment") do (getPolySeg verts w h figSeg curSp knoti true IsSmoothJoin )
				if (segStr == "System.Windows.Media.PolyBezierSegment") do (getPolySeg verts w h figSeg curSp knoti false IsSmoothJoin )
	
			) -- iter_segs
	
			append polys verts.count
			
		) -- iter_figs
	
		
		undo off
		(
			newmesh = Trimesh()
			setmesh newmesh vertices:verts faces:#() -- materialIDS:#(1,2)
			
			for p = (polys.count-1) to 1 by -2 do
			(
				vertnumberarr = #{polys[p]..polys[p+1]} as array
				
				-- Check if startpoint is inside previous poly
				if(p<(polys.count-1)) then
				(
					startpoint = meshop.getvert newmesh vertnumberarr[1]
					if ( (isInside newmesh startpoint) == false ) then
						meshop.createPolygon newmesh vertnumberarr
					
				) else meshop.createPolygon newmesh vertnumberarr
				
				update newmesh
			)
			
			meshop.flipNormals newmesh #all
			update newmesh
		)
		
		-- remove isolated verts
		meshop.deleteIsoVerts newmesh
		
		-- get verts
		num_verts = newmesh.numverts
		
		for v = 1 to num_verts do
			append newverts (getVert newmesh v)

		-- copy verts
		newverts = verts
		
		-- get faces
		num_faces = newmesh.numfaces
		
		for f = 1 to num_faces do
			append newfaces (getFace newmesh f)

		-- Garbage Collection
		dotNetGC = dotnetclass "System.GC"
		dotNetGC.collect()
	)	

)


4 Replies

Probably you have your own reason to make it your way but using MAX you can do it easier:

  1. Create Text Shape
  2. Apply Shell modifier
  3. Convert To … if necessary

PS. Times Roman (compatible with Times New Roman), Helvetica (compatible with Arial) and Courier (compatible with Courier New)

Hi denis!

Yes, that is true. I can do it that way manually, or creating a standard plugin for it. But I have my reasons. One is that that I already have an extensive SimpleObject plugin (not the sample one I attached) that I need the Truetype functionality into, and a simpleObject does not cooperate well with creating shape-nodes and “CreateInstance Text” does not leave you with the option to convert it to a mesh.

Another reason is that I in the future want more control over the “splineshape” to “mesh” conversion, so I can apply my own features as I want them.

I am currently working on my idea on how to cut the polygons, and will post it as soon as the prototype is finnished, perhaps someone could help me sort it out to function better, and somewhat optimize it

Updated version with first attempt at cutting polys, see function: cutHole. It does not perform well, and I fail to find what goes wrong



/*

	TTF to mesh
	version 0.1.1
	Last Edited: 28/03/2010

	by Peter Larsen 2010
	
	This plugin requires .Net framework 3.0.
	Tested using 3dsmax v9 SP2

	This sourcecode is Free to use in any way you may want to. Use this at your own risk.
	
	History:	v1.1.1	First attempt on creating a hole cutting function: "cutHole"
	
				v0.1	This version lacks feature to cut holes in the mesh shapes.
						Code is not optimized and poor error handling routines.
						Fontlist collector is not dotnet, but regedit hack.
						
	Todo:				Cut proper holes in geometry instead of crazy holes.
						Optimized code, better error handling.
						Collecting fonts using dotnet classes.
						Interpolating curves.

*/

plugin simpleObject LarsenTtfToMesh_plugin_def

name:"TtfToMesh"
classID:#(666666,888888)
category:"Scripted Primitives"
( 
	local ttfonts = #()	
	
	local ttfverts = #()
	local ttffaces = #()
	
	local createFontList, ttftomesh, updateText, isInside 
	
	parameters ttftmeshParams rollout:ROttftmesh
	(
		TTFToMesh_height type:#worldUnits ui:TTFToMesh_height 	default:0	animatable:true
		TTFToMesh_width type:#worldUnits ui:TTFToMesh_width 	default:0 animatable:true
		TTFToMesh_depth type:#worldUnits ui:TTFToMesh_depth		default:0 animatable:true
		
		selfont  type:#string ui:lbttFonts 					animatable:true
		seltext  type:#string ui:ttfText 	default:"Test" 	animatable:true
		
		cbCutHoles type:#boolean ui:cbCutHoles 				animatable:true

	)
	
	rollout params "About" rolledUp:true
	(
	      label about1 "\"TTF to mesh\""
		  label about2 "v0.1"
		  label about3 "by Peter Larsen 2010"
	)
	
	rollout ROttftmesh "Main Parameters" rolledUp:false
	(
		spinner TTFToMesh_height	"Height" 	type:#worldunits 	range:[-1000,1000,0]
		spinner TTFToMesh_width 	"Width" 	type:#worldunits 	range:[-1000,1000,0]
		spinner TTFToMesh_depth		"Depth" 	type:#worldunits 	range:[-1000,1000,0]
	
		group "Text and font"
		(
			dropdownlist lbttFonts "Font"
			edittext ttfText "Text :" fieldWidth:100 labelOnTop:true text: "Test"
		)
		
		checkbox cbCutHoles "Cut Holes" checked:false triState:0
		
		on ttfText changed newStr do
		(
				updateText()
		) -- on ttfText changed state
		
		on lbttFonts selected sel do
		(
				updateText()
		) -- on ttfText changed state
		
		on cbCutHoles changed state do
		(
				updateText()
		) -- on ttfText changed state



		on ROttftmesh open do
		(
			setWaitCursor()
			
			-- fontlist
			if ttfonts.count == 0 do
					createFontList ttfonts
		
			if ttfonts.count != 0 do
				if lbttFonts.items.count == 0 do
					lbttFonts.items = ttfonts
					
			updateText()			
			setArrowCursor()

		) -- on ROttftmesh open
	) -- rollout ROttftmesh
	
	
	on buildMesh do
	(
		disableSceneRedraw() 
    	suspendEditing() 
		
		local vertsBx = #()
	
		undo off
		(
			if autosave.Enable do autosave.resettimer() -- reset AS timer (just for safe)
		
			setWaitCursor()
			local timeStart = timestamp()


			if( (ttfverts.count==0) or (ttffaces.count==0) ) then
			(
			
				-- create empty dummy
				setMesh mesh \
					verts: #([0,0,0], [TTFToMesh_width, 0, 0], [TTFToMesh_width, TTFToMesh_depth, TTFToMesh_height])  \
					faces: #([3,2,1])
			
			) else (

				for v = 1 to ttfverts.count do
				(			
					-- set size
					vertsBx [v] = [	ttfverts[v][1] * TTFToMesh_width,
									ttfverts[v][2] * TTFToMesh_depth,
									ttfverts[v][3] * TTFToMesh_height  ]
				)	
	
				-- create mesh
				setMesh mesh \
					verts: vertsBx  \
					faces: ttffaces
	
				
			)
	
			meshop.extrudeFaces mesh #{1..(ttffaces.count)} (TTFToMesh_height) 0 dir:#common 
	
			update mesh

			local timeEnd = timestamp()
			setArrowCursor()
			format "
Total Time:		% sec.
" ((timeEnd - timeStart)/1000.0)

		)
		
		resumeEditing() 
 		enableSceneRedraw()
		redrawviews()
		
		gc()
	)
	
	tool create
	(
		on mousePoint click do
		(
			case click of
			(
				1: nodeTM.translation = gridPoint
				3: #stop
			)
		) -- on mousePoint click
	
		on mouseMove click do
		(		
			case click of
			(
				2: (TTFToMesh_width = gridDist.x; TTFToMesh_depth = gridDist.y)
				3: TTFToMesh_height = gridDist.z
			)
		) -- on mouseMove click
	) -- tool create


	function updateText =
	(
		-- get font and text
		sel = ROttftmesh.lbttFonts.selection
		if(sel >0) do
		(
			selfont = ROttftmesh.lbttFonts.items[sel]
			seltext = ROttftmesh.ttfText.text
			
			ttfverts = #()
			ttffaces = #()
			
			if (seltext.count > 0) do
				ttftomesh ttfverts ttffaces selfont seltext
		)
	)

	fn existFile fname = (getfiles fname).count != 0

	function createFontList ttfonts =
	(
			-- directories
			tmppath= (Getdir #temp ) + "\	tfonts\	ruetype.txt"
			tmppath2= (Getdir #temp ) + "\	tfonts\\ascii_tt.txt"
	
			-- Check
			if ((existFile tmppath) == false) do
			(
				makeDir ((Getdir #temp) + "\	tfonts")
				cmdstr = "regedit /e \"" + tmppath +  "\" \"HKEY_LOCAL_MACHINE\software\Microsoft\Windows NT\CurrentVersion\Fonts\""
				DOSCommand cmdstr
				
				-- Unicode to ascii
				cmdstr = "type \"" + tmppath +  "\" > \"" + tmppath2 +  "\""
				DOSCommand cmdstr
			)
			
			if ttfonts.count == 0 do
			(
				-- load ascii_tt.txt
				ttffs = openFile tmppath2
				
				if(ttffs != undefined) then
				(
					tmpStr = ""
					oldtmpStr = ""
					while not eof ttffs do
					(
						oldtmpStr = copy tmpStr
						tmpStr = readDelimitedString ttffs "\""
						
						if(tmpStr[1] == "=") do
						(
							tmpfname_arr = filterString oldtmpStr "("
							tmpfnametr = trimRight tmpfname_arr[1]
							append ttfonts (copy tmpfnametr)
						)
					)
					
					sort ttfonts 		
					close ttffs
				)		
			)
	) -- function createFontList 


	fn cutHole newmesh vertnumberarr vertnumberbitarray removefaces =
	(
		--	1. Iterate “hole"-verts, check what face the vert is inside, divide that face
		--	2. Iterate “hole"-verts again, cutting edges where no edge is between the verts
		--	3. Somehow select all faces inside “hole” edges, and delete the faces.
		
		-- add faces to mesh
		numVerts	= meshop.getNumVerts newmesh
		numFaces 	= meshop.getNumFaces newmesh
		vertCount 	= vertnumberarr.count
		
		local newEdges = #()
		local holeverts = #()
		local cutNormal = getFaceNormal newmesh 1
		
		-- 1: iterate and split faces
		for hv = 1 to vertCount do
		(
			
			holevert = vertnumberarr[hv]
			holepoint = meshop.getvert newmesh holevert 
			
			-- find face
			f = isinside true newmesh holepoint
			
			-- divide face on holepoint
			-- get the original face verts
			if(f>0) do
			(	
				numVerts += 1
				append holeverts numVerts
			
				origfverts = getFace newmesh f

				-- get bary
				bary = meshop.getBaryCoords newmesh f holepoint
				-- divide face
				meshop.divideFace newmesh f baryCoord:bary 

				update newmesh
								
				-- store created edges for later use
				vertEdges = meshop.getEdgesUsingVert newmesh #{numVerts}
				vertEdgesArr = vertEdges as array
				faceEdges = meshop.getEdgesUsingFace newmesh #{f}
				
				append newedges ((vertEdges * faceEdges ) as array ) 
				
			)
			update newmesh 
		)
		
		-- 2: cut where no edge exists
		for he = 1 to newEdges.count do
		(	
		
			he2 = he+1
			if(he2 > newEdges.count) do he2=1
			
			-- check that edge isn't already between verts
			holevert1 = holeverts[he]
			holevert2 = holeverts[he2]

			holevert1edges = ((meshop.getEdgesUsingVert newmesh #(holevert1) ) as array)
			holevert2edges = ((meshop.getEdgesUsingVert newmesh #(holevert2) ) as array)

			-- iterate edges, to make sure they already do not share any edge
			edgeexists = false
			for e1 = 1 to holevert1edges.count do
				for e2 = 1 to holevert2edges.count do
					if ( holevert1edges[e1] == holevert2edges[e2] ) do
						edgeexists = true
						
			-- if there is no edge, cut!
			if ( edgeexists == false ) do
				try(
					-- get the edges
					
						edge1 = newEdges[he][2]
						edge2 = newEdges[he2][2]
					
						if (edge2 == edge1) do
							edge2 = newEdges[he2][1]
			
						--meshop.turnEdge newmesh edge1
						meshop.cut newmesh edge1 (0.0) edge2 (0.0) (cutNormal) fixNeighbors:false split:true
		
					update newmesh
				) catch ()
		) -- for he = 1 to newEdges.count do
		
				
		-- 3: remove inner faces
		holevertfaces = (meshop.getFacesUsingVert newmesh vertnumberarr) as array
		for hf = 1 to holevertfaces.count do
		(
			-- find faces where all verts is holeverts (vertnumberbitarray)
			faceverts = (meshop.getVertsUsingFace newmesh #(holevertfaces[hf])) as array
			
			innerface = true
			for fv = 1 to faceverts.count do
			(
				vertindex = faceverts[fv]
				if (vertnumberbitarray[vertindex] == false) do
					innerface = false
			)
			
			-- if innerface, add to removeface array 
			if (innerface == true) do
				append removefaces holevertfaces[hf]
		)
		
		
		
	) -- fn cutHole newmesh vertnumberarr 

	fn isInside getface obj testpoint =
	(
		local sum = 0
		local f = 1
		
		numFaces = meshop.getNumFaces obj
		
		-- loop all faces, if 360 then point is inside face
		while ( (sum < 350) and (f <= numFaces) ) do
		(
			-- Get 3 edges
			local faceEdges = (meshop.getEdgesUsingFace obj f) 
			faceEdges = faceEdges as array
		
			sum = 0
			
			-- Loop 3 edges
			for i = 1 to faceEdges.count do
			(
				-- Get edge verts
				local edgeVerts = meshop.getVertsUsingEdge obj faceEdges[i]
				edgeVerts = edgeVerts as array
				
				-- Get verts position and normalize vectors to testpoint
				if(edgeVerts.count == 2) do
				(
					local n1 = normalize ( (meshop.getVert obj edgeVerts[1]) - testpoint)
					local n2 = normalize ( (meshop.getVert obj edgeVerts[2]) - testpoint)
					
					-- sum vectors
					sum += acos (dot n1 n2)
				)
				
			)

			f += 1		
		)
		
		-- if 360 then then point is inside
		if ( getface == true) then
			if (sum>=350) then
				return (f-1)
			else
				return 0
		else if(sum>=350) then
			return true
		else
			return false
	)
	
	
	fn getSeg verts w h figSeg curSp knoti corner =
	(
		-- Get Point
		segPoint = figSeg.Point
		
		append verts  ([segPoint.x/w, segPoint.y/h, 0])
		
	) -- fn getSeg
	
	fn getPolySeg verts w h figSeg curSp knoti line corner =
	(
		-- Get Point Collection
		segPointCollection = figSeg.Points
		points = segPointCollection.CloneCurrentValue()
	
		-- Count, Enumerator
		pointCount 	= points.count
		pointEnum 	= points.GetEnumerator()
	
		for iter_points = 1 to pointCount do
		(
			-- Get current Point
			pointEnum.MoveNext()
			segPoint = pointEnum.Current
				
			append verts  ([segPoint.x/w, segPoint.y/h, 0])
			
		) -- iter_points
	) -- fn getPolySeg


	function ttftomesh newverts newfaces sfont stext=
	(
		-- loading dotnet assemblies
		dotnet.loadassembly "PresentationCore"
		dotnet.loadassembly "System"
		
		-- classes
		geoClass = dotNetClass "System.Windows.Media.PathGeometry"
		
		-- Init FormattedText arguments
		txClass = dotNetClass "System.String"
		txObj	= dotNetObject txClass stext
		
		fsClass = dotNetClass "System.Windows.FontStyle"
		fsObj 	= dotNetObject fsClass (dotNetClass "FontStyles.Normal")
		
		fwClass = dotNetClass "System.Windows.FontWeight"
		fwObj 	= dotNetObject fwClass (dotNetClass "FontWeights.Medium")
		
		ciClass = dotNetClass "System.Globalization.CultureInfo"
		ciObj	= dotNetObject ciClass "en-us"
		ciObj2	= ciObj.GetCultureInfo "en-us"
		
		fdClass	= dotNetClass "System.Windows.FlowDirection"
		fdObj	= fdClass.LeftToRight
		
		tfClass	= dotNetClass "System.Windows.Media.Typeface"
		tfObj	= dotNetObject tfClass sfont
		
		siObj = dotNetObject "System.Double" 32
		
		brClass = dotNetClass "System.Windows.Media.SolidColorBrush"
		brObj	= dotNetObject brClass 
		
		ftClass = dotNetClass "System.Windows.Media.FormattedText"
		
		-- Create FormattedText instance
		ftObj 	= dotNetObject ftClass txObj ciOBj2 fdObj tfObj siObj brObj
		
		-- Position Point class and Object
		ptClass = dotNetClass "System.Windows.Point"
		ptObj	= dotNetObject ptClass 0 0
		
		-- Call BuildGeometry Method, returns Geometry
		geoObj	= ftObj.BuildGeometry ptObj
		
		-- Get Bounding Bounds
		boundsRect = getProperty geoObj #Bounds asDotNetObject:true
		
		-- Get width and height from Rect
		w = getProperty boundsRect #Width
		h = getProperty boundsRect #Height
		
		-- Get path
		geoPath = geoObj.GetOutlinedPathGeometry()
		
		-- Get figures
		geoPathClone = geoPath.CloneCurrentValue()
	
		geoFigures = getProperty geoPathClone #Figures asDotNetObject:true
		geoFillrule = getProperty geoPathClone #Fillrule asDotNetObject:true

		-- Get Figures count
		geoFigCount = geoFigures.Count
		
		-- Get Figure Item
		figEnum = geoFigures.GetEnumerator()
			
		-- Variables
		verts = #()
		polys = #()
	
		for iter_figs = 1 to geoFigCount do
		(
			figEnum.MoveNext()
			geoFig0 = figEnum.Current
		
			-- Get a Figure startpoint "System.Windows.Point"
			geoStartPoint	= geoFig0.startpoint
			-- isclosed
			figIsClosed 	= geoFig0.IsClosed
			-- isFilled
			figIsFilled 	= geoFig0.IsFilled
	
			append verts ([geoStartPoint.x / w, geoStartPoint.y / h, 0])
			append polys verts.count
			
			-- Get Segments Collection
			geoPathFigureSegCollection = geoFig0.Segments
			segs = geoPathFigureSegCollection.CloneCurrentValue()
			
			segsCount = segs.count
			segsEnum = segs.GetEnumerator()
			for iter_segs = 1 to segsCount do
			(
				-- Get current Segment
				segsEnum.MoveNext()
				figSeg = segsEnum.Current
				
				-- Get Segment Type
				segStr = figSeg.ToString()
				
				-- IsSmoothJoin
				IsSmoothJoin 	= figSeg.IsSmoothJoin
				-- IsStroked
				IsStroked		= figSeg.IsStroked
								
				if (segStr == "System.Windows.Media.LineSegment") do (getSeg verts w h figSeg curSp knoti IsSmoothJoin)
				if (segStr == "System.Windows.Media.PolyLineSegment") do (getPolySeg verts w h figSeg curSp knoti true IsSmoothJoin )
				if (segStr == "System.Windows.Media.PolyBezierSegment") do (getPolySeg verts w h figSeg curSp knoti false IsSmoothJoin )
	
			) -- iter_segs
	
			append polys verts.count
			
		) -- iter_figs
	
		
		holepolyverts = #()
		
		undo off
		(
			newmesh = Trimesh()
			setmesh newmesh vertices:verts faces:#() -- materialIDS:#(1,2)
			
			for p = (polys.count-1) to 1 by -2 do
			(
				vertnumberbitarray = #{polys[p]..polys[p+1]}
				vertnumberarr = vertnumberbitarray as array
				
				-- Check if startpoint is inside previous poly
				if(p<(polys.count-1)) then
				(
					startpoint = meshop.getvert newmesh vertnumberarr[1]
					if ( (isInside false newmesh startpoint) == false ) then
					(					
						meshop.createPolygon newmesh vertnumberarr
					) else ( 
						append holepolyverts polys[p]
						append holepolyverts polys[p+1]
					)
											
				) else meshop.createPolygon newmesh vertnumberarr
				
				update newmesh
			)
			
			-- cut holes
			removefaces = #()
			if (ROttftmesh.cbCutHoles.checked == true) do
				for p = 1 to holepolyverts.count by 2 do
				(
					vertnumberbitarray = #{holepolyverts[p]..holepolyverts[p+1]}
					vertnumberarr = vertnumberbitarray as array

					cutHole newmesh vertnumberarr vertnumberbitarray removefaces
				)
			
			if(removefaces.count > 0) do
				meshop.deleteFaces newmesh removefaces  delIsoVerts:false
				
			meshop.removeDegenerateFaces newmesh
			meshop.removeIllegalFaces newmesh
			
			meshop.flipNormals newmesh #all
			update newmesh
			
		) -- undo off
		
		-- remove isolated verts
		meshop.deleteIsoVerts newmesh
		
		update newmesh
		
		-- get verts
		num_verts = newmesh.numverts
		
		for v = 1 to num_verts do
			append newverts (getVert newmesh v)

		-- copy verts
		newverts = verts
		
		-- get faces
		num_faces = newmesh.numfaces
		
		for f = 1 to num_faces do
			append newfaces (getFace newmesh f)
		
		-- Garbage Collection
		dotNetGC = dotnetclass "System.GC"
		dotNetGC.collect()
		
	) -- function ttftomesh newverts newfaces sfont stext

)


I think I go try this solution: Ear-clipping of polygons

Does anyone have maxscript code for that type of algorithm ?

Some progress is achieved, but still not there, look at attached image, see the “PH” letters for example and you see my problem.

If someone can find the bug/bugs in my ugly code I would be very happy! because it is giving me headache