Notifications
Clear all

[Closed] Identify illegal characters in objects or materials

Hi,
I’m trying to write a function that will allow me to pass, say an object’s name and then it is scanned for illegal characters as per idenitifed in an array and then flag as true or false.
I have been looking at Neil Blevin’s functions and think the best way is something along the lines of:

fn sLibRemoveTailNumbers s =
	(
	s2 = (sLibReverseString s)
	if s2.count != 0 then
		(
		nums = "01234567890" 
		for i = 1 to s2.count do
			(
			j = findString nums s2[i]
			if (j == undefined) do 
				(
				newS = sLibReverseString (substring s2 i s2.count)
				return newS
				)
			)
		)
	)
	
fn sLibReverseString s =
	(
	newS = ""
	if s.count != 0 then
		(
		for i = s.count to 1 by -1 do newS += s[i]
		)
	return newS
	)

But I haven’t quite got my head around how to achieve this function! Any help would be appreciated.

illegalcharactersArr=#(“”,”/”,”:”)

For example, I wrote this function, but it feels highly in-efficient, can anyone improve on the concept outlined above?

fn illegalCharRemove objs =
(
for i in 1 to objects.count do
	(
	while matchpattern (objects[i].name as string) pattern:"*:*" == true do
		(
		w = findstring objects[i].name ":"
		namestring=objects[i].name
		if w == 1 or w == (namestring.count) then
			(
			objects[i].name = substring (objects[i].name) (w as integer) -1
			)
		else objects[i].name = replace (objects[i].name) (w as integer) 1 "_"
		)
	i += 1
	)
)
illegalCharRemove objs

The biggest problem with the simple function above is that it only picks up 1 iteration of 1 certain ‘illegal’ character and then changes it to a “_” symbol. This means the function has to be run 3 times to capture say, 3 intances of the “:” character in the object name! Rubbish!

Thanks,
Mike

8 Replies
struct replaceIllegalCharsDef
(
	illegalChars = "\"-':", --can also be an array of strings/chars
	legalChar = "_",
	
	fn replaceIllegals str =
	(
		for i = 1 to illegalChars.count do
		(
			if matchPattern str pattern:("*" + illegalChars[i] + "*") then
			(
				local ind = findString str illegalChars[i]
				str = replace str ind 1 legalChar
			)
		)--end for
		str
	),
	
	fn fixIllegalObject obj =	--return false if name was illegal
	(
		local newName = replaceIllegals obj.name
		if newName == obj.name then
			true
		else
		(
			obj.name = newName
			false
		)
		--replaceIllegals obj.mat.name  --can also do material
	),
	
	fn fixIllegalObjects objs =
	(
		for obj in objs collect (fixIllegalObject obj)	--will collect true or false for each object
	)
)

(
	rep = replaceIllegalCharsDef()
	rep.fixIllegalObjects objects
)

That is good and easily expanded, clearly laid out code. You specify your variables at the top of the struct. Should be self-explanatory (work from the bottom up when reading it)

hmm Rob… I don’t think your code actually accounts for what Mike mentioned at the end, though; multiple occurrences?

e.g.


illegalChars = "*.?|\\"
legalChar = "_"
myStr = "Hello. Is this a *test*? || illegal\\filename.ext"
"Hello. Is this a *test*? || illegal\filename.ext"
replaceIllegals myStr
"Hello_ Is this a _test*_ _| illegal_filename.ext"

You basically have to build a loop where it continues to try and ‘search and replace’ until it no longer finds a match. Here’s a function we use in our lib that does a search&replace of all the ‘f’ character and replaces it with the string ‘r’ except where preceded by character defined in ‘x’ (optional).
You’ll see that it uses substituteString if available (and x is unsupplied) that was added in 3ds Max 2008 (previously in the AVGuard extensions, so it just checks if the function is available) and automatically substitutes all occurrences of the search string (can be more than 1 character).


fn strFindAndReplaceAllCS str f r x: = (
	if ((x == unsupplied) AND (substituteString != undefined)) do (
		return (substituteString str f r)
	)
	local strTemp; local fcount = f.count
	for i = 1 to (str.count - fCount + 1) do (
		strTemp = subString str i fCount
		if (strTemp == f) do (
			if (str[i - 1] != x) do (
				str = replace str i fCount r
			)
		)
	)
	return str
)

Using just the substituteString, you could do, e.g:


for i = 1 to illegalChars.count do ( myStr = substituteString myStr illegalChars[i] legalChar )
[color=Blue]OK
myStr
"Hello_ Is this a _test__ __ illegal_filename_ext"

Another method would be using filterString();


filterString myStr illegalChars
#("Hello", " Is this a ", "test", " ", " illegal", "filename", "ext")

After which you can tack them back together into a new string.
[/color]

Thanks for the advice Rob/Richard.
Richard,
I went with the following approach, but I need to carry out 2 more slight advancements to the script if you could provide some more top tips!


illegalChars = " :;'@#~[{}]|!£$%^&*()-=+\\,<.>//?"
ReplaceChar = "_"
for i = 1 to illegalChars.count do
	(
	if (substituteString != undefined) then
		(
		for n = 1 to objects.count do (objects[n].name = substituteString objects[n].name illegalChars[i] ReplaceChar)
		)
	)

Things to do after the code has run above:
– 1. Get rid of underscores if its at the beginning or end of a string.
– 2. If multiple “_” characters next to each other, then remove all but just 1.

As I will be processing the whole scene, I need to have this code really optimized for speed!
If possible, it would be ideal to process the additional code within the existing loop…!

Thanks,
Mike


  str = "__Hello____World__"
  "__Hello____World__"
  str = trimLeft (trimRight str "_") "_"
  "Hello____World"
  

  while (matchPattern str pattern:"*__*") do (
    str = substituteString str "__" "_"
  )
  "Hello_World"
  

Not sure if the above is the fastest method – perhaps a loop would be faster… would have to test

Edit: A loop is indeed slightly faster, at least on a single long string.


 newStr = ""
 underscore = false
 for i = 1 to str2.count do (
 	chr = str2[i]
 	if (chr == "_") then (
 		if (not underscore) then (
 			underscore = true
 			append newStr chr
 		)
 	)
 	else (
 		underscore = false
 		append newStr chr
 	)
 )
 

Edit2: And on a few thousand shorter strings (~20 characters), it still appears to win. The matchpattern/substitutestring one wins if the strings are much shorter, 5/10 characters, but only marginally.

Thanks Richard…!
This seems to working like a dream now…
You seem to be on fire today, maybe I should post a thread on my other tricky issue I’m having at the moment as well…! See if you’re up to the challenge…
Regards,
Mike

If you simply want to replace/remove any non-alphanumeric characters (which I assume is what you’re after as you have “ :;’@#~[{}]|!£$%^&*()-=+\,<.>//?” marked as illegal characters in your post), you could also use this regular expression:

(dotNetObject “System.Text.RegularExpressions.Regex” “[^\w]”).Replace SourceString ReplacementString

Hope this helps,
Martijn

If you -are- going to go with way-of-the-future .NET, abstract the dotnet object… e.g.


dno_regexreplace = (dotNetObject "System.Text.RegularExpressions.Regex" "[^\w]").Replace

-- loop bits here
dno_regexreplace str ReplaceChar

Otherwise it’s actually slower than the way-of-the-dodo native maxscript as it has to look up and construct the dotnetobject each time.

Hi again,
I seem to have hit a wall when it comes to renaming layers in 3dsMax…some help would be much appreciated! The following code is failing to rename the layer names…Any ideas…anyone?

-- Replace ALL illegalCharacters in Layers as defined by "illegalChars" and replaced by the character defined in "ReplaceChar"
illegalChars = " :;'@#~[{}]|!£$%^&*()-=+\\,<.>//?"
ReplaceChar = "_"
for i = 1 to illegalChars.count do
	(
	if (substituteString != undefined) then
		(
		for n = 1 to LayerManager.count-1 do
			(
			nlayer = LayerManager.getLayer n
			NewLayerName = substituteString nlayer.name illegalChars[i] ReplaceChar
			nlayer.setname = (NewLayerName as string)
			)
		)
	)--End initial loop

Mike

EDIT: I’m a twat. Just worked it out. Its a method, so no need to have the “=”

nlayer.setname (NewLayerName as string)