티스토리 뷰

카테고리 없음

TOUCHDESIGNER_ img to svg

잉_민 2024. 4. 26. 19:41

https://velog.io/@ggong/SVG-%EC%95%8C%EA%B3%A0-%EC%93%B0%EA%B8%B0-%EC%B5%9C%EC%A0%81%ED%99%94

 

SVG 구조, 렌더링 성능 알고 쓰기

웹페이지를 구성하면서 이미지나 아이콘을 사용할때 svg를 많이 쓴다. 디바이스 화면에 따라 2x, 3x처럼 최적화된 화질의 이미지를 따로 설정해주는 것은 아주 귀찮고, 벡터 기반으로 되어 있는 sv

velog.io

svg 파일 구조는 어떻게 될까?

svg에서는 더 복잡한 도형을 그리기 위해 <path> 도형을 사용할 수 있다. <path> 도형은 패스 데이터(d)를 통해 도형의 모양을 그리는데, 이 패스 데이터에는 패스를 그리는 moveTo, lineTo, closePath, curve 등을 의미하는 각 명령 이름좌표값이 들어있다. 데이터에 명시된 좌표값과 명령문을 이용해 도형을 이루는 선을 그리게 된다.

<svg>
  <path 
    d="M248.761,92c0,9.801-7.93,17.731-17.71,17.731c-0.319,0-0.617,0-0.935-0.021c-10.035,37.291-51.174,65.206-100.414,65.206 c-49.261,0-90.443-27.979-100.435-65.334c-0.765,0.106-1.531,0.149-2.317,0.149c-9.78,0-17.71-7.93-17.71-17.731 c0-9.78,7.93-17.71,17.71-17.71c0.787,0,1.552,0.042,2.317,0.149C39.238,37.084,80.419,9.083,129.702,9.083c49.24,0,90.379,27.937,100.414,65.228h0.021c0.298-0.021,0.617-0.021,0.914-0.021C240.831,74.29,248.761,82.22,248.761,92z" 
    fill="#f9ef21" stroke="#f9cf01" stroke-width="7" stroke-linejoin="round" />
</svg>

 

svg의 컨테이너 요소

  • <g> 요소 : 그룹화를 위한 컨테이너 요소.
  • <use> 요소 : 문서 전반에서 요소를 재사용할 수 있다. xlint:href="#식별자ID"처럼 쓰면 재사용할 요소를 호출하여 사용할 수 있다.
  • <defs> 요소 : <defs> 요소내에 선언된 그래픽은 svg 뷰포트에 렌더링되지 않는다. 렌더링하려면 <use>를 통해 참조해야 한다. 약간 js의 변수 선언 같다는 느낌을 받았다.

아래 코드로 컨테이너 요소들을 사용하는 방법에 대한 예시를 알아볼 수 있다.

<svg width="512" height="512" viewBox="0 0 512 512">
  
  <!-- 그래픽 정의 defs -->
  <defs>
    <!-- 그래픽 3개를 묶은 그룹 요소 선언 -->
    <g id="cherry-tree-group">
      <g id="fruit-cherry" transform="translate(0 100) scale(0.3)">...</g>
      <use xlink:href="#fruit-cherry" x="50" y="100" />
      <use xlink:href="#fruit-cherry" x="150" />
    </g>
  </defs>
  
  <!-- defs 내부에 선언된 그래픽 3개 그룹 재사용 -->
  <use xlink:href="#cherry-tree-group" />
  <use xlink:href="#cherry-tree-group" x="200" y="100" />
  
</svg>

TD : img -> SOP trace -> svg

 

svg 파일에 좌표값 -> G- code의 위치로 변환.

library 

import svgpathtools
import svgwrite

(td pip 를 통해 설치)

https://ing-min.tistory.com/231

 

Touchdesinger- 콘다 환경 및 라이브러리 불러오기

https://derivative.ca/community-post/tutorial/anaconda-managing-python-environments-and-3rd-party-libraries-touchdesigner Anaconda - Managing Python Environments and 3rd-Party Libraries in TouchDesigner Bananaconda - This amazing article picture is brought

ing-min.tistory.com

save -> text, table 오퍼레이트에 저장되게 

수정했다.

>>수정한 svgEXT

'''SVG Write in TouchDesigner Class
Authors | matthew ragan
matthewragan.com
'''
from io import StringIO
import svgpathtools
import svgwrite
import numpy as np
import math
import webbrowser

class Soptosvg:
	'''
	This class is inteded to handle writing SVGs from SOPs in TouchDesigner.
	
	This is a largely experimental approach so there are bound to be things
	that are wrong or don't work. 

	---------------
	
	'''

	def __init__( self ):
		''' This is the init method for the Soptosvg process
		'''
		self.Polylinesop 			= parent.svg.par.Polylinesop
		self.Polygonsop				= parent.svg.par.Polygonsop
		self.Svgtype 				= parent.svg.par.Svgtype
		self.Filepath 				= "{dir}/{file}.svg"
		self.UseCamera 				= parent.svg.par.Usecamera
		self.Camera 				= parent.svg.par.Camera
		self.Aspect 				= (parent.svg.par.Aspect1, parent.svg.par.Aspect2)

		self.Axidocumentation 		= "http://wiki.evilmadscientist.com/AxiDraw"
		self.Axipdf 				= "http://cdn.evilmadscientist.com/wiki/axidraw/software/AxiDraw_V33.pdf"
		self.Svgwritedocumentation 	= "http://svgwrite.readthedocs.io/en/latest/svgwrite.html"

		print( "Sop to SVG Initialized" )
		return


	def WorldToCam(self, oldP):
		'''Method to convert worldspace coords to cameraspace coords.

		Args
		-------------
		oldP (tdu.Position) : the tdu.Position to convert to camera space.

		Returns
		-------------
		newP (tuple) : tuple of x,y coordinates after camera projection. 

		'''
		camera 		= op(self.Camera.eval())
		view 		= camera.transform()
		view.invert()
		pers 		= camera.projection( self.Aspect[0].eval(), self.Aspect[1].eval() )
		viewP 		= view * oldP
		adjusted 	= pers * viewP 
		newX 		= adjusted.x/adjusted.z
		newY 		= adjusted.y/adjusted.z
		newP 		= (newX, newY)

		return newP

	def Canvas_size(self):
		''' This is a helper method to return the dimensions of the canvas.

		Having an output size for the SVG isn't necessary, but for working with the axi-draw
		it's often helpful to have your file set-up and ready to plot from if possible.
		This method grabs the par of the svgWrite tox and returns those dimensions to other methods.
						
		Notes
		---------------

		Args
		---------------
		none

		Returns
		---------------
		canvassize (tupple) : a tupple of width and height dimensions measured in millimeters
		'''
		canvassize 					= None

		if parent.svg.par.Canvassize == 'letter':
			canvassize 				= ('279.4mm','279.4mm')
		elif parent.svg.par.Canvassize == 'A4':
			canvassize 				= ('210mm','297mm')
		elif parent.svg.par.Canvassize == 'Custom':
			canvassize = (str(parent.svg.par.Customwidth) + 'mm', str(parent.svg.par.Customheight) + 'mm')


		return canvassize

	def Par_check(self, svg_type):
		''' Par_check() is an error handling method.

		Par_check aims to ensrue that all parameters are correctly set up so we can advance to the
		steps of creating our SVGs. This means checking to ensure that all needed fields are
		completed in the TOX. If we pass all of the par check tests then we can move on to 
		writing our SVG file to disk.
						
		Notes
		---------------
		'self' does not need to be included in the Args section.

		Args
		---------------
		svg_type (str): the string name for the inteded type of output - polygon, polyline, both

		Returns
		---------------
		ready (bool) : the results of a set of logical checks to that ensures all requisite 
						pars have been supplied for a sucessful write to disk for the file.
		'''		

		ready 				= False

		title 				= "We're off the RAILS!"
		message 			= '''Hey there, things don't look totally right.
Check on these parameters to make sure everything is in order:\n{}'''
		buttons 			= ['okay']
		checklist 			= []

		# error handling for geometry permutations
		# handling polyline saving
		if self.Svgtype 	== 'pline':
			if self.Polylinesop != None and op(self.Polylinesop).isSOP:
				pass
			else:
				checklist.append( 'Missing Polygon SOP' )

		# handling polygon saving
		elif self.Svgtype 	== 'pgon':
			if self.Polygonsop != None and op(self.Polygonsop).isSOP:
				pass
			else:
				checklist.append( 'Missing Polyline SOP' )

		# handling combined objects - polyline and polygon saving
		elif self.Svgtype 	== 'both':
			polyline 		= self.Polylinesop != None and op(self.Polylinesop).isSOP
			polygon 		= self.Polygonsop != None and op(self.Polygonsop).isSOP

			# both sops are present
			if polyline and polygon:
				pass
			# missing polyline sop
			elif polygon and not polyline:
				checklist.append( 'Missing Polyline SOP' )
			# missing polygon sop
			elif polyline and not polygon:
				checklist.append( 'Missing Polygon SOP' )
			# missing both polyline and polygon sops
			elif not polyline and not polygon:
				checklist.append( 'Missing Polygon SOP')
				checklist.append( 'Missing Polyline SOP')

		# handling to check for a directory path
		if parent.svg.par.Dir == None or parent.svg.par.Dir.val == '':
			checklist.append( 'Missing Directory Path' )
		
		else:
			pass

		# handling to check for a file path
		if parent.svg.par.Filename == None or parent.svg.par.Filename.val == '':
			checklist.append( 'Missing File name' )

		# Check for camera
		if parent.svg.par.Usecamera:
			if parent.svg.par.Camera == None or op(parent.svg.par.Camera).type != "cam":
				checklist.append( 'Missing Camera' )

		else:
			pass		

		# we're in the clear, everything is ready to go
		if len(checklist) 			== 0:
			ready 					= True
		# correctly format message for ui.messageBox and warn user about missing elements
		else:
			ready 					= False	
			messageChecklist 		= '\n'
			for item in checklist:
				messageChecklist 	+= '     * {}\n'.format(item)
			
			message 				= message.format(messageChecklist)
			ui.messageBox(title, message, buttons=buttons)
		return ready
		


	def generate_gcode(self, svg_data, feedrate=1000, z_safe=5, z_cut=1):
		svg_file = StringIO(svg_data)
		paths, attributes, svg_attributes = svgpathtools.svg2paths2(svg_file)

		gcode = []
		gcode.append("G21")  # Set units to millimeters
		gcode.append("G90")  # Set absolute positioning
		gcode.append(f"G0 Z-{z_safe}")  # Move pen up to safe height

		for path in paths:
			start = path.point(0)
			gcode.append(f"G0 X{start.real:.3f} Y{start.imag:.3f}")  # Move to start position
			gcode.append(f"G1 Z{z_cut} F{feedrate}")  # Lower pen to drawing depth

			for line in path:
				end = line.end
				gcode.append(f"G1 X{end.real:.3f} Y{end.imag:.3f}")

			gcode.append(f"G0 Z-{z_safe}")  # Raise pen to safe height

		gcode.append("G0 X0 Y0")  # Move to origin
		gcode.append("M2")  # End program

		return "\n".join(gcode)

	def Save(self):
	    ''' This is the Save method, used to start the process of writing the svg to disk.
	
	    Based on settings in the tox's parameters the Save() method will utilize other
	    helper methods to correctly save out the file. Pragmatically, this means first
	    ensuring that all pars are correctly set up (error prevention), then the
	    appropriate calling of other methods to ensure that geometry is correclty
	    written to file.
	    
	    Notes
	    ---------------
	    none
	
	    Args
	    ---------------
	    none
	
	    Returns
	    ---------------
	    none
	    '''
	
	    # get the svg type
	    svgtype = self.Svgtype
	
	    # start with Par_check to see if we're ready to proced.
	    readyToContinue = self.Par_check(svgtype)
	
	    if readyToContinue:
	        filepath = self.Filepath.format(dir=parent.svg.par.Dir, file=parent.svg.par.Filename)
	
	        if svgtype == 'pline':
	            svg_data = self.GetPolylineSVG(pline=op(self.Polylinesop))
	        elif svgtype == 'pgon':
	            svg_data = self.GetPolygonSVG(pgon=op(self.Polygonsop))
	        elif svgtype == 'both':
	            svg_data = self.GetPolygonAndPolygonSVG(pline=op(self.Polylinesop), pgon=op(self.Polygonsop))
	        else:
	            print("Woah... something is very wrong")
	            return
	
	        # Save SVG data to 'text1' DAT
	        n= op('text2')
	        n.text  = svg_data
	
	        # Generate G-code from the SVG data
	        gcode = self.generate_gcode(svg_data)

	        # Save G-code to 'table1' DAT
	        op('table1').text = gcode
	
	        print(f"SVG data saved to: text1")
	        print(f"G-code saved to: table1")
	
	    else:
	        pass
	
	    return
	
	def GetPolylineSVG(self, pline):
	    Canvassize = self.Canvas_size()
	    prims = pline.prims
	    dwg = svgwrite.Drawing('dummy.svg', profile='tiny', size=Canvassize)
	
	    for item in prims:
	        if self.UseCamera:
	            newPoints = [self.WorldToCam(vert.point.P) for vert in item]
	        else:
	            newPoints = [(vert.point.x, vert.point.y) for vert in item]
	        newPoly = dwg.polyline(points=newPoints, stroke='black', stroke_width=1, fill='none')
	        dwg.add(newPoly)
	
	    return dwg.tostring()
	
	def GetPolygonSVG(self, pgon):
	    Canvassize = self.Canvas_size()
	    prims = pgon.prims
	    dwg = svgwrite.Drawing('dummy.svg', profile='tiny', size=Canvassize)
	
	    for item in prims:
	        if self.UseCamera:
	            newPoints = [self.WorldToCam(vert.point.P) for vert in item]
	        else:
	            newPoints = [(vert.point.x, vert.point.y) for vert in item]
	        newPoly = dwg.polygon(points=newPoints, stroke='black', stroke_width=1, fill='none')
	        dwg.add(newPoly)
	
	    return dwg.tostring()
	
	def GetPolygonAndPolygonSVG(self, pline, pgon):
	    Canvassize = self.Canvas_size()
	    pgonPrims = pgon.prims
	    plinePrims = pline.prims
	    dwg = svgwrite.Drawing('dummy.svg', profile='tiny', size=Canvassize)
	
	    for item in pgonPrims:
	        newPoints = []
	        if self.UseCamera:
	            for vert in item:
	                p = self.WorldToCam(vert.point.P)
	                if not math.isnan(p[0]):
	                    newPoints.append(p)
	        else:
	            for vert in item:
	                p = (vert.point.x, vert.point.y)
	                if not math.isnan(p[0]):
	                    newPoints.append(p)
	        if newPoints:
	            newPoly = dwg.polygon(points=newPoints, stroke='black', stroke_width=1, fill='none')
	        if newPoly:
	            dwg.add(newPoly)
	
	    for item in plinePrims:
	        newPoints = []
	        if self.UseCamera:
	            for vert in item:
	                p = self.WorldToCam(vert.point.P)
	                if not math.isnan(p[0]):
	                    newPoints.append(p)
	        else:
	            for vert in item:
	                p = (vert.point.x, vert.point.y)
	                if not math.isnan(p[0]):
	                    newPoints.append(p)
	        if newPoints:
	            newPoly = dwg.polyline(points=newPoints, stroke='black', stroke_width=1, fill='none')
	        if newPoly:
	            dwg.add(newPoly)
	
	    return dwg.tostring()

 

SOP 데이터를 SVG 형식으로 변환하는 코드 (앞서 논의한 svgEXT 확장)
SVG 데이터를 G 코드로 변환하는 코드 (generate_gcode 함수)
G 코드를 CNC 기계로 전송하는 시리얼 통신 설정 (예: TouchDesigner의 'serial1' 오퍼레이터)


CHOP인스턴스 - SOP

https://github.com/raganmd/touchdesigner-sop-to-svg/tree/dev

 

GitHub - raganmd/touchdesigner-sop-to-svg: A pipeline for handling the SOP to SVG pipeline. This is especially handy for using p

A pipeline for handling the SOP to SVG pipeline. This is especially handy for using procedurally generated geometry for paths to be cut or plotted. - raganmd/touchdesigner-sop-to-svg

github.com

SVG(Scalable Vector Graphics)는 래스터 그래픽 규칙에 얽매이지 않는 이미지를 생성하는 편리한 수단입니다. 일러스트레이터와 디자이너는 다양한 목적으로 SVG를 사용하는 경우가 많지만 플로팅 및 절단 기계와 상호 작용할 때 특히 유용합니다. 레이저 및 비닐 절단기, 플로터 및 기타 모든 장치는 래스터 기반 그래픽이 아닌 SVG를 사용할 수 있습니다.

Derivative의 TouchDesigner는 래스터 기반 그래픽 작업으로 잘 알려져 있지만 내장된 접근 방식으로 SVG를 캡처하고 출력하는 기능은 거의 지원되지 않습니다. 그러나 이 작업을 처리할 수 있는 강력한 Python 계층을 지원합니다. 이 TouchDesigner 모듈의 기본 설계는 SOP(Surface Operator)를 SVG로 변환하여 플롯하거나 레이저 절단할 수 있도록 하는 프로세스를 목표로 합니다.

<여기에 구체적인 방법 잘 설명되어있음.>

나는 이 프로젝트를 변형할거임.

 


G-CODE 생성 문제 해결

project_cnc.tox
0.00MB

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함