Page 1 of 1

Import DMX Fixtures from CSV / SVG

Posted: Mon Nov 14, 2022 2:49 pm
by mad-matt
MadMapper 5.2.0 added new possibilities for importing fixtures from CSV, you can now set your fixture name and group hierarchy in a CSV file column. For instance "Stage Fixtures/Fixture 1" will end making a fixture called "Fixture 1" in a group called "Stage Fixtures". If the group doesn't yet exists, it will create it.
Also fixtures will be placed in the order you created it in the CSV (first fixture in the CSV will be on top of all others in the fixtures list in MadMapper)

An example CSV is attached. Here is the content:
Fixture Definition Name;Start Universe;Start Channel;StartX;StartY;EndX;EndY;Width;Fixture Name (optional)
Generic - Pixel RGB;0;1;0;0.5;2;0.5;1;Group-1/Pixel-1
Generic - Pixel RGB;0;4;2;0.5;4;0.5;1;Group-1/Pixel-2
Generic - Pixel RGB;0;7;4;0.5;6;0.5;1;Group-1/Pixel-3
Generic - Pixel RGB;0;10;6;0.5;8;0.5;1;Group-2/Pixel-4
Generic - Pixel RGB;0;13;8;0.5;10;0.5;1;Group-2/Pixel-5
Generic - Pixel RGB;0;16;10;0.5;12;0.5;1;Group-2/Pixel-6
Generic - Pixel RGB;0;19;12;0.5;14;0.5;1;Group-2/SubGroup-1/Pixel-7
Generic - Pixel RGB;0;22;14;0.5;16;0.5;1;Group-2/SubGroup-1/Pixel-8
Generic - Pixel RGB;0;25;16;0.5;18;0.5;1;Group-2/SubGroup-1/Pixel-9
The first field is the Fixture definition name, which can either contain the fixture definition group or not (we could have written "Pixel RGB" in this case)
Then comes start universe & start channel. You must know that or refer to MadLight documentation.
StartX, StartY, EndX, EndY & Width will be used to position the fixture. All values are in pixels. There are two cases here: fixture line or other type of fixture (quad or circle). For quad or circle fixtures, the input rectangle will be placed with top left at (StartX,StartY) and bottom left at (EndX, EndY) & Width is ignored. For Fixture Lines, the line will start at (StartX,StartY) and end at (EndX,EndY), the width parameter will define line width.
Then comes Fixture Name column which was described above. If a fixture with the same name already exists, we'll append a number.

SVG import hasn't changed in 5.2.0. The idea here is to extract only the SVG Line primitives and create a fixture for each. It is possible to add fixture name & DMX patching on each SVG Line primitive as following:
<line fixtureDefinition="Generic - 60x1L" fixtureUniverse="0" fixtureChannel="1" stroke="#000000" x1="65" x2="7" y1="7" y2="65" />
An example is provided as attachment.

Please post here if you have any question. We might bundle better documentation in a PDF.

Re: Import DMX Fixtures from CSV / SVG

Posted: Thu Apr 13, 2023 1:30 pm
by fekril
Great, but what's the point, we're trying to understand how this function could improve our production flow, we can't find it, could you give us a concrete example for which this function improves the production flow or its quality of realization

Re: Import DMX Fixtures from CSV / SVG

Posted: Fri Apr 14, 2023 12:28 pm
by mad-matt
If you have hundreds of LED bars, you might prefer to handle the patching of each with formulas in an excel sheet;
If you have a complex structure, you might want to place lines in Illustrator and use SVG format.
We once did a project with more than 400 bars, we used a python script to generate an SVG that includes DMX patching, quite handy. The visuals were created from the same SVG so everything matches perfectly.
This one was smaller but same principle, same SVG for fixtures and for content

Screenshot 2023-04-14 at 12.26.40.png
Screenshot 2023-04-14 at 12.26.40.png (842.13 KiB) Viewed 8274 times
Screenshot 2023-04-14 at 12.27.10.png
Screenshot 2023-04-14 at 12.27.10.png (518.83 KiB) Viewed 8274 times

Re: Import DMX Fixtures from CSV / SVG

Posted: Sun Apr 16, 2023 10:43 am
by fekril
ok, thank's , you said "We once did a project with more than 400 bars, we used a python script to generate an SVG that includes DMX patching, quite handy,"

can you share with us your python script to generate an SVG that includes the DMX patch?

Re: Import DMX Fixtures from CSV / SVG

Posted: Mon Apr 17, 2023 11:30 am
by mad-matt
Sure

Here is a complex python script that generates the SVG for this project: https://www.instagram.com/p/BNg9xQOhktu/
It adds DMX patching & some data in SVG XML elements because it was loaded by a handmade generator doing particules.
It also generates an OBJ file for 3D preview
import xml.etree.cElementTree as ET
import math
import sys, getopt

# Final setup values
countX = 14
countY = 11

pixelsPerBar = 59
hardwarePitch = 3
exportPitch = 3
targetFile = "bars.svg"
exportText = False

# Set exportPitch to hardwarePitch for the file to import fixture from
# Set exportPitch to zero for the file we use in Le Metropol module

argv = sys.argv[1:]

try:
opts, args = getopt.getopt(argv,"hto:p:e:",[])
except getopt.GetoptError:
print 'generate_svg.py -o <outputfile> -p <hardwarePitch> -e <exportPitch> -t'
print 'examples'
print ' python generate_svg.py -o fixtures.svg -p 3 -e 3 -t'
print ' python generate_svg.py -o bars.svg -p 3 -e 0'
sys.exit(2)
for opt, arg in opts:
if opt in ("-o"):
targetFile = arg
elif opt in ("-t"):
exportText = True
elif opt in ("-p"):
hardwarePitch = int(arg)
elif opt in ("-e"):
exportPitch = int(arg)

gridSizeX = countX * 2
gridSizeY = countY

margin = 4

resX = gridSizeX * (pixelsPerBar + hardwarePitch*2)
resY = gridSizeY * (pixelsPerBar + hardwarePitch*2)

svg = ET.Element("svg", version="1.1", width=str(resX+hardwarePitch*2 + 2*margin), height=str(resY-hardwarePitch*2 + 2*margin))

def getPointPos(x,y):
return [margin + x * (pixelsPerBar + hardwarePitch*2), margin + y * (pixelsPerBar + hardwarePitch*2)]

def getPoint3DPos(x,y):
rotAngle = 2 * 3.141592654 * x / gridSizeX;
return [gridSizeX*(pixelsPerBar + hardwarePitch*2) * math.cos(rotAngle), y * (pixelsPerBar + hardwarePitch*2), gridSizeX*(pixelsPerBar + hardwarePitch*2) * math.sin(rotAngle)]

# Each bar extremity point has a number so we can compute connections between bars
def getPointId(x,y):
if x >= gridSizeX:
return getPointId(0,y)
else:
return x/2 + y*countX

patchingLines=[[[-1,10],[0,9],[-1,8],[-1,6],[0,5],[-1,4],[-1,2],[0,1],[-1,0]],[[0,11],[-1,10],[-1,8],[0,7],[-1,6],[-1,4],[0,3],[-1,2],[-1,0]],[[0,11],[0,9],[1,8],[0,7],[0,5],[1,4],[0,3],[0,1],[1,0]],[[0,11],[1,10],[0,9],[0,7],[1,6],[0,5],[0,3],[1,2],[0,1]]]

def getArtNetPatchForSegment(x1,y1,x2,y2):
if x1%2 == 0:
baseX = x1
elif x2%2 == 0:
baseX = x2
else:
assert (x1==x2), "Should be a vertical on odd numbers"
baseX = x1 + 1

relativeX1 = x1 - baseX
relativeX2 = x2 - baseX
baseUniverse = ((baseX%gridSizeX) / 2) * 4
for line in range(0,4):
patchingLine = patchingLines[line]
for idInLine in range(0,len(patchingLine)-1):
patchP1 = patchingLine[idInLine]
patchP2 = patchingLine[idInLine+1]

universe = baseUniverse+line
if universe >= 16:
universe += 24-16
if universe >= 24+20:
universe += 24-20

if patchP1[0] == relativeX1 and patchP1[1] == y1 and patchP2[0] == relativeX2 and patchP2[1] == y2:
return {"universe":universe, "channel":1+idInLine * 60,"reversed":False}
if patchP1[0] == relativeX2 and patchP1[1] == y2 and patchP2[0] == relativeX1 and patchP2[1] == y1:
return {"universe":universe, "channel":1+idInLine * 60,"reversed":True}

print("Error: patching not found for " + str(x1) + "," + str(y1) + " - " + str(x2) + "," + str(y2))
print(" relative: " + str(relativeX1) + "," + str(y1) + " - " + str(relativeX2) + "," + str(y2))
print(" baseX: " + str(baseX))
return {}

# Create the SVG file
for y in range(0,gridSizeY+1):
for x in range(0,gridSizeX+1):
if x % 2 != y % 2:
# 3 lines going down from this point
segStartId = getPointId(x,y)

if y < gridSizeY and x > 0: # Down left
segStart = getPointPos(x,y)
segStart[0] -= exportPitch
segStart[1] += exportPitch
segEnd = getPointPos(x-1,y+1)
segEndId = getPointId(x-1,y+1)
segEnd[0] += exportPitch
segEnd[1] -= exportPitch
segmentId = segStartId * 3 + 0;

artNetPatch=getArtNetPatchForSegment(x,y,x-1,y+1)

if artNetPatch["reversed"]:
ET.SubElement(svg, "line", x2=str(segStart[0]), y2=str(segStart[1]), x1=str(segEnd[0]), y1=str(segEnd[1]), fill="none", stroke="#000000", fixtureDefinition="Generic - 60x1L", id="l"+str(segmentId), endPointId=str(segStartId), startPointId=str(segEndId), fixtureUniverse=str(artNetPatch["universe"]), fixtureChannel=str(artNetPatch["channel"]))
else:
ET.SubElement(svg, "line", x1=str(segStart[0]), y1=str(segStart[1]), x2=str(segEnd[0]), y2=str(segEnd[1]), fill="none", stroke="#000000", fixtureDefinition="Generic - 60x1L", id="l"+str(segmentId), startPointId=str(segStartId), endPointId=str(segEndId), fixtureUniverse=str(artNetPatch["universe"]), fixtureChannel=str(artNetPatch["channel"]))

if exportText:
textElem = ET.SubElement(svg, "text", x=str((segStart[0]+segEnd[0])/2), y=str((segStart[1]+segEnd[1])/2), fill="red")
textElem.text = str(segmentId)
if y < gridSizeY-1: # Straight Down
segStart = getPointPos(x,y)
segStart[1] += exportPitch
segEnd = getPointPos(x,y+2)
segEndId = getPointId(x,y+2)
segEnd[1] -= exportPitch
segmentId = segStartId * 3 + 1;

artNetPatch=getArtNetPatchForSegment(x,y,x,y+2)

if artNetPatch["reversed"]:
ET.SubElement(svg, "line", x2=str(segStart[0]), y2=str(segStart[1]), x1=str(segEnd[0]), y1=str(segEnd[1]), fill="none", stroke="#000000", fixtureDefinition="Generic - 60x1L", id="l"+str(segmentId), endPointId=str(segStartId), startPointId=str(segEndId), fixtureUniverse=str(artNetPatch["universe"]), fixtureChannel=str(artNetPatch["channel"]))
else:
ET.SubElement(svg, "line", x1=str(segStart[0]), y1=str(segStart[1]), x2=str(segEnd[0]), y2=str(segEnd[1]), fill="none", stroke="#000000", fixtureDefinition="Generic - 60x1L", id="l"+str(segmentId), startPointId=str(segStartId), endPointId=str(segEndId), fixtureUniverse=str(artNetPatch["universe"]), fixtureChannel=str(artNetPatch["channel"]))

if exportText:
textElem = ET.SubElement(svg, "text", x=str((segStart[0]+segEnd[0])/2), y=str((segStart[1]+segEnd[1])/2), fill="red")
textElem.text = str(segmentId)
if y < gridSizeY and x < gridSizeX: # Down right
segStart = getPointPos(x,y)
segStart[0] += exportPitch
segStart[1] += exportPitch
segEnd = getPointPos(x+1,y+1)
segEndId = getPointId(x+1,y+1)
segEnd[0] -= exportPitch
segEnd[1] -= exportPitch
segmentId = segStartId * 3 + 2;

artNetPatch=getArtNetPatchForSegment(x,y,x+1,y+1)

if artNetPatch["reversed"]:
ET.SubElement(svg, "line", x2=str(segStart[0]), y2=str(segStart[1]), x1=str(segEnd[0]), y1=str(segEnd[1]), fill="none", stroke="#000000", fixtureDefinition="Generic - 60x1L", id="l"+str(segmentId), endPointId=str(segStartId), startPointId=str(segEndId), fixtureUniverse=str(artNetPatch["universe"]), fixtureChannel=str(artNetPatch["channel"]))
else:
ET.SubElement(svg, "line", x1=str(segStart[0]), y1=str(segStart[1]), x2=str(segEnd[0]), y2=str(segEnd[1]), fill="none", stroke="#000000", fixtureDefinition="Generic - 60x1L", id="l"+str(segmentId), startPointId=str(segStartId), endPointId=str(segEndId), fixtureUniverse=str(artNetPatch["universe"]), fixtureChannel=str(artNetPatch["channel"]))

if exportText:
textElem = ET.SubElement(svg, "text", x=str((segStart[0]+segEnd[0])/2), y=str((segStart[1]+segEnd[1])/2), fill="red")
textElem.text = str(segmentId)

segStart = getPointPos(x,y)

if exportText:
textElem = ET.SubElement(svg, "text", x=str(segStart[0]), y=str(segStart[1]+1), fill="green")
textElem.text = str(segStartId)

tree = ET.ElementTree(svg)

tree.write(targetFile)

# Create a 3D OBJ file for preview
objFileContent = ""
vIndices = {}
vIdx = 1

# Vertices
for x in range(0,gridSizeX):
vIndices[x]={}
for y in range(0,gridSizeY+1):
if x % 2 != y % 2:
pos3D = getPoint3DPos(x,y)
objFileContent += "v " + str(pos3D[0]) + " " + str(pos3D[1]) + " " + str(pos3D[2]) + "\n"
vIndices[x][y] = vIdx
vIdx += 1

objFileContent += "\n"

for x in range(0,gridSizeX+1):
for y in range(0,gridSizeY+1):
if x % 2 != y % 2:
if y < gridSizeY and x > 0: # Down left
objFileContent += "f " + str(vIndices[(x) % gridSizeX][y]) + " " + str(vIndices[(x-1) % gridSizeX][y+1]) + " " + str(vIndices[(x) % gridSizeX][y]) + "\n"
pass

if y < gridSizeY-1: # Straight Down
objFileContent += "f " + str(vIndices[(x) % gridSizeX][y]) + " " + str(vIndices[(x) % gridSizeX][y+2]) + " " + str(vIndices[(x) % gridSizeX][y]) + "\n"
pass

if y < gridSizeY and x < gridSizeX: # Down right
objFileContent += "f " + str(vIndices[(x) % gridSizeX][y]) + " " + str(vIndices[(x+1) % gridSizeX][y+1]) + " " + str(vIndices[(x) % gridSizeX][y]) + "\n"
pass

text_file = open("bars.obj", "w")
text_file.write(objFileContent)
text_file.close()

Re: Import DMX Fixtures from CSV / SVG

Posted: Tue May 30, 2023 9:36 am
by vnkbln
Thank you for importing fixtures! This is a very handy feature for large projects!

Can you please tell me how to set the fixture type when importing? I can import DMX fixtures, but not DMX lines (I need fixtures with arrows for directions)

Image

Re: Import DMX Fixtures from CSV / SVG

Posted: Thu Jun 01, 2023 1:22 am
by mad-matt
Indeed, generally when importing from CSV/SVG we don't modify the mapping after, but if you need you might want a "Fixture Line". We should add this option.

Re: Import DMX Fixtures from CSV / SVG

Posted: Fri Jun 02, 2023 8:47 am
by vnkbln
Fixture direction helps a lot with setup and troubleshooting, so it would be great if you add the ability to import "Fixture Line"

Re: Import DMX Fixtures from CSV / SVG

Posted: Mon Jun 19, 2023 10:34 am
by mad-matt
Okay that's in our whishlist.

Re: Import DMX Fixtures from CSV / SVG

Posted: Tue Sep 26, 2023 2:29 pm
by mad-matt
Another information, there's a option in SVG to specify the fixture line (as well as DMX universe/channel) width when importing from SVG, example:

<svg height="717" version="1.1" width="1834">
<line fill="none" id="Group 1/Fixture 1" stroke="#000000" x1="0" y1="0" x2="512" y2="512" fixtureDefinition="Generic - 60x1L" fixtureUniverse="2" fixtureChannel="1" fixtureWidthInPixels="5"/>
<line fill="none" id="Group 1/Fixture 2" stroke="#000000" x1="0" y1="1024" x2="512" y2="512" fixtureDefinition="Generic - 60x1L" fixtureUniverse="2" fixtureChannel="61" fixtureWidthInPixels="5"/>
<line fill="none" id="Group 2/Fixture 3" stroke="#000000" x1="1024" y1="0" x2="512" y2="512" fixtureDefinition="Generic - 60x1L" fixtureUniverse="3" fixtureChannel="1" fixtureWidthInPixels="50"/>
<line fill="none" id="Group 2/Fixture 4" stroke="#000000" x1="1024" y1="1024" x2="512" y2="512" fixtureDefinition="Generic - 60x1L" fixtureUniverse="3" fixtureChannel="61" fixtureWidthInPixels="50"/>
</svg>