티스토리 뷰
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에서는 더 복잡한 도형을 그리기 위해 <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
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
SVG(Scalable Vector Graphics)는 래스터 그래픽 규칙에 얽매이지 않는 이미지를 생성하는 편리한 수단입니다. 일러스트레이터와 디자이너는 다양한 목적으로 SVG를 사용하는 경우가 많지만 플로팅 및 절단 기계와 상호 작용할 때 특히 유용합니다. 레이저 및 비닐 절단기, 플로터 및 기타 모든 장치는 래스터 기반 그래픽이 아닌 SVG를 사용할 수 있습니다.
Derivative의 TouchDesigner는 래스터 기반 그래픽 작업으로 잘 알려져 있지만 내장된 접근 방식으로 SVG를 캡처하고 출력하는 기능은 거의 지원되지 않습니다. 그러나 이 작업을 처리할 수 있는 강력한 Python 계층을 지원합니다. 이 TouchDesigner 모듈의 기본 설계는 SOP(Surface Operator)를 SVG로 변환하여 플롯하거나 레이저 절단할 수 있도록 하는 프로세스를 목표로 합니다.
<여기에 구체적인 방법 잘 설명되어있음.>
나는 이 프로젝트를 변형할거임.
G-CODE 생성 문제 해결
- Total
- Today
- Yesterday
- Express
- motor controll
- DeepLeaning
- oculuspro
- three.js
- houdini
- Arduino
- ardity
- Java
- AI
- 유니티
- MQTT
- CNC
- docker
- Unity
- colab
- TouchDesigner
- JacobianMatrices
- RNN
- 후디니
- unity 360
- VR
- Python
- emotive eeg
- 라즈베리파이
- opencv
- node.js
- StableDiffusion
- 유니티플러그인
- sequelize
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |