Skip to content

Instantly share code, notes, and snippets.

@ChrisvanChip
Last active April 4, 2023 05:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ChrisvanChip/c7bdf7e15353fbe8c77432f28cf3147f to your computer and use it in GitHub Desktop.
Save ChrisvanChip/c7bdf7e15353fbe8c77432f28cf3147f to your computer and use it in GitHub Desktop.
const __html__ = "<a id=\"download\" href=\"#\" download=\"export.rbxmx\" style=\"display:none;\">Download</a>\r\n<button id=\"convselect\">Convert Selection</button>\r\n<button id=\"closeplugin\">Close Plugin</button>\r\n<script>\r\n document.getElementById(\"closeplugin\").onclick = () => {\r\n parent.postMessage({ pluginMessage: { type: 'close-plugin' } }, '*')\r\n }\r\n document.getElementById(\"convselect\").onclick = () => {\r\n parent.postMessage({ pluginMessage: { type: 'exec' } }, '*')\r\n }\r\n \r\n onmessage = (event) => {\r\n const { type, data } = event.data.pluginMessage;\r\n \r\n if (type === \"Download\") {\r\n const DownloadLink = document.getElementById(\"download\");\r\n\r\n DownloadLink.href = \"data:text/xml;charset=utf-8,\" + encodeURIComponent(data);\r\n DownloadLink.click();\r\n }\r\n }\r\n</script>";/*
BETA VERSION
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@bbbbbbbb@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@FFFFFFFFFFFFFFFFFFFFFF@@iiii@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@tttt@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@RRRRRRRRRRRRRRRRR@@@@@@@@@@@@@@@@@@@b::::::b@@@@@@@@@@@@lllllll@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@F::::::::::::::::::::F@i::::i@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ttt:::t@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@R::::::::::::::::R@@@@@@@@@@@@@@@@@@b::::::b@@@@@@@@@@@@l:::::l@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@F::::::::::::::::::::F@@iiii@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@t:::::t@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@R::::::RRRRRR:::::R@@@@@@@@@@@@@@@@@b::::::b@@@@@@@@@@@@l:::::l@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@FF::::::FFFFFFFFF::::F@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@t:::::t@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@RR:::::R@@@@@R:::::R@@@@@@@@@@@@@@@@@b:::::b@@@@@@@@@@@@l:::::l@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@F:::::F@@@@@@@FFFFFFiiiiiii@@@@ggggggggg@@@ggggg@@@mmmmmmm@@@@mmmmmmm@@@@@aaaaaaaaaaaaa@@@@@@@@ttttttt:::::ttttttt@@@@@@@ooooooooooo@@@@@@@@@@R::::R@@@@@R:::::R@@@ooooooooooo@@@b:::::bbbbbbbbb@@@@@l::::l@@@@ooooooooooo@xxxxxxx@@@@@@xxxxxxx@@@@@
@@@@@@@F:::::F@@@@@@@@@@@@@i:::::i@@@g:::::::::ggg::::g@mm:::::::m@@m:::::::mm@@@a::::::::::::a@@@@@@@t:::::::::::::::::t@@@@@oo:::::::::::oo@@@@@@@@R::::R@@@@@R:::::R@oo:::::::::::oo@b::::::::::::::bb@@@l::::l@@oo:::::::::::oox:::::x@@@@x:::::x@@@@@@
@@@@@@@F::::::FFFFFFFFFF@@@@i::::i@@g:::::::::::::::::gm::::::::::mm::::::::::m@@aaaaaaaaa:::::a@@@@@@t:::::::::::::::::t@@@@o:::::::::::::::o@@@@@@@R::::RRRRRR:::::R@o:::::::::::::::ob::::::::::::::::b@@l::::l@o:::::::::::::::ox:::::x@@x:::::x@@@@@@@
@@@@@@@F:::::::::::::::F@@@@i::::i@g::::::ggggg::::::ggm::::::::::::::::::::::m@@@@@@@@@@@a::::a@@@@@@tttttt:::::::tttttt@@@@o:::::ooooo:::::o@@@@@@@R:::::::::::::RR@@o:::::ooooo:::::ob:::::bbbbb:::::::b@l::::l@o:::::ooooo:::::o@x:::::xx:::::x@@@@@@@@
@@@@@@@F:::::::::::::::F@@@@i::::i@g:::::g@@@@@g:::::g@m:::::mmm::::::mmm:::::m@@@@aaaaaaa:::::a@@@@@@@@@@@@t:::::t@@@@@@@@@@o::::o@@@@@o::::o@@@@@@@R::::RRRRRR:::::R@o::::o@@@@@o::::ob:::::b@@@@b::::::b@l::::l@o::::o@@@@@o::::o@@x::::::::::x@@@@@@@@@
@@@@@@@F::::::FFFFFFFFFF@@@@i::::i@g:::::g@@@@@g:::::g@m::::m@@@m::::m@@@m::::m@@aa::::::::::::a@@@@@@@@@@@@t:::::t@@@@@@@@@@o::::o@@@@@o::::o@@@@@@@R::::R@@@@@R:::::Ro::::o@@@@@o::::ob:::::b@@@@@b:::::b@l::::l@o::::o@@@@@o::::o@@@x::::::::x@@@@@@@@@@
@@@@@@@F:::::F@@@@@@@@@@@@@@i::::i@g:::::g@@@@@g:::::g@m::::m@@@m::::m@@@m::::m@a::::aaaa::::::a@@@@@@@@@@@@t:::::t@@@@@@@@@@o::::o@@@@@o::::o@@@@@@@R::::R@@@@@R:::::Ro::::o@@@@@o::::ob:::::b@@@@@b:::::b@l::::l@o::::o@@@@@o::::o@@@x::::::::x@@@@@@@@@@
@@@@@@@F:::::F@@@@@@@@@@@@@@i::::i@g::::::g@@@@g:::::g@m::::m@@@m::::m@@@m::::ma::::a@@@@a:::::a@@@@@@@@@@@@t:::::t@@@@tttttto::::o@@@@@o::::o@@@@@@@R::::R@@@@@R:::::Ro::::o@@@@@o::::ob:::::b@@@@@b:::::b@l::::l@o::::o@@@@@o::::o@@x::::::::::x@@@@@@@@@
@@@@@FF:::::::FF@@@@@@@@@@@i::::::ig:::::::ggggg:::::g@m::::m@@@m::::m@@@m::::ma::::a@@@@a:::::a@@@@@@@@@@@@t::::::tttt:::::to:::::ooooo:::::o@@@@@RR:::::R@@@@@R:::::Ro:::::ooooo:::::ob:::::bbbbbb::::::bl::::::lo:::::ooooo:::::o@x:::::xx:::::x@@@@@@@@
@@@@@F::::::::FF@@@@@@@@@@@i::::::i@g::::::::::::::::g@m::::m@@@m::::m@@@m::::ma:::::aaaa::::::a@@@@@@@@@@@@tt::::::::::::::to:::::::::::::::o@@@@@R::::::R@@@@@R:::::Ro:::::::::::::::ob::::::::::::::::b@l::::::lo:::::::::::::::ox:::::x@@x:::::x@@@@@@@
@@@@@F::::::::FF@@@@@@@@@@@i::::::i@@gg::::::::::::::g@m::::m@@@m::::m@@@m::::m@a::::::::::aa:::a@@@@@@@@@@@@@tt:::::::::::tt@oo:::::::::::oo@@@@@@R::::::R@@@@@R:::::R@oo:::::::::::oo@b:::::::::::::::b@@l::::::l@oo:::::::::::oox:::::x@@@@x:::::x@@@@@@
@@@@@FFFFFFFFFFF@@@@@@@@@@@iiiiiiii@@@@gggggggg::::::g@mmmmmm@@@mmmmmm@@@mmmmmm@@aaaaaaaaaa@@aaaa@@@@@@@@@@@@@@@ttttttttttt@@@@@ooooooooooo@@@@@@@@RRRRRRRR@@@@@RRRRRRR@@@ooooooooooo@@@bbbbbbbbbbbbbbbb@@@llllllll@@@ooooooooooo@xxxxxxx@@@@@@xxxxxxx@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@g:::::g@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@gggggg@@@@@@g:::::g@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@g:::::gg@@@gg:::::g@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@g::::::ggg:::::::g@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@gg:::::::::::::g@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ggg::::::ggg@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@gggggg@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Version 0.1.15
By NoTwistedHere
This plugin is free to use, report any bugs to me on Discord (NoTwistedHere#0001)
(This is probably the worst code I've ever written, but it works)
Limitations:
Only supports linear gradients
Only one fill colour per layer
Effects are not supported (e.g. drop shadows, inner shadows, etc. May be added in the future)
Vectors are not supported, only Rectangles, Ellipses, and Text (lines are partially supported)
Rotation is not currently supported due to complications, so to save yourself some time, don't rotate your layers before running the plugin (rotate after once imported into your project)
Known Issues:
Changelog:
=== [UNTRACKED] ===
*/
var HandledError = false;
var CurrentNotif;
function QuickClose(Message) {
if (CurrentNotif !== undefined) CurrentNotif.cancel();
HandledError = true;
figma.notify(`Error: ` + Message, { timeout: 10000 });
figma.closePlugin();
throw new Error(Message);
}
function Notify(Message) {
if (CurrentNotif !== undefined) CurrentNotif.cancel();
CurrentNotif = figma.notify(Message);
}
function getGradientRotation(gradientTransform) {
const a = gradientTransform[0][0];
const b = gradientTransform[0][1];
const angle = Math.atan2(b, a) * 180 / Math.PI;
return angle >= 0 ? angle : angle + 360;
}
function LimitDecimals(Number, Decimals) { // Limit decimals to x places and round up/down
return parseFloat(Number.toFixed(Decimals));
}
const Fonts = {
["Thin"]: {
Weight: 100,
Style: "Normal",
},
["ExtraLight"]: {
Weight: 200,
Style: "Normal",
},
["Light"]: {
Weight: 300,
Style: "Normal",
},
["Regular"]: {
Weight: 400,
Style: "Normal",
},
["Medium"]: {
Weight: 500,
Style: "Normal",
},
["SemiBold"]: {
Weight: 600,
Style: "Normal",
},
["Bold"]: {
Weight: 700,
Style: "Normal",
},
["ExtraBold"]: {
Weight: 800,
Style: "Normal",
},
["Black"]: {
Weight: 900,
Style: "Normal",
},
["Thin Italic"]: {
Weight: 100,
Style: "Italic",
},
["ExtraLight Italic"]: {
Weight: 200,
Style: "Italic",
},
["Light Italic"]: {
Weight: 300,
Style: "Italic",
},
["Italic"]: { // Regular Italic
Weight: 400,
Style: "Italic",
},
["Medium Italic"]: {
Weight: 500,
Style: "Italic",
},
["SemiBold Italic"]: {
Weight: 600,
Style: "Italic",
},
["Bold Italic"]: {
Weight: 700,
Style: "Italic",
},
["ExtraBold Italic"]: {
Weight: 800,
Style: "Italic",
},
["Black Italic"]: {
Weight: 900,
Style: "Italic",
},
}
const LineJoinModes = [
"Round",
"Bevel",
"Miter"
]
const TextXAlignments = [
"LEFT",
"RIGHT",
"CENTER",
]
const TextYAlignments = [
"TOP",
"CENTER",
"BOTTOM",
]
const PropertyTypes = {
["children"]: (Element, Properties) => {
for (var i = 0; i < Element.children.length; i++) {
Properties.Children.push(GetMainProperties(Element.children[i], Properties));
}
/*
// TODO: Re-implement masks
var Mask = [];
var Children = [];
var children = Element.children // .reverse();
for (var i = 0; i < children.length; i++) {
if (children[i].isMask == true) {
Mask.push(children[i]);
} else {
Children.push(children[i]);
}
}
if (Mask.length > 1) {
return QuickClose("Multiple masks are unsupported, on element: " + Element.name);
} else if (Mask.length == 1) {
var MaskProperties = ElementTypes["GROUP"](Mask[0], Properties) // GetMainProperties(Mask[0], Properties)
MaskProperties.ClipsDescendants = true;
for (var i = 0; i < Children.length; i++) {
MaskProperties.Children.push(GetMainProperties(Children[i], MaskProperties));
}
Properties.Children.push(MaskProperties);
}*/
},
["fills"]: (Element, Properties) => {
if (Element.fills.length > 1) {
return QuickClose("Multiple fills are unsupported, on element: " + Element.name);
} else if (Element.fills.length == 0) {
Properties.BackgroundColor3 = {R: 0, G: 0, B: 0}; // TODO: default to missing texture
}
const Filler = Element.fills[0];
if (!Filler) return;
switch (Filler.type) {
case "SOLID":
var Colour = {
R: Filler.color.r,
G: Filler.color.g,
B: Filler.color.b,
};
if (Properties.Class == "TextLabel") {
Properties.TextColor3 = Colour;
} else {
Properties.BackgroundColor3 = Colour;
}
break;
case "GRADIENT_LINEAR":
if (Properties.Class == "TextLabel") {
Properties.TextColor3 = {R: 1, G: 1, B: 1};
} else {
Properties.BackgroundColor3 = {R: 1, G: 1, B: 1};
}
const Transform = Filler.gradientTransform;
const Rotation = getGradientRotation(Transform);
Properties.Children.push({
Class: "UIGradient",
Type: "UIGradient",
Transparency: 1 - Filler.opacity,
Enabled: Filler.visible,
Colour: Filler.gradientStops.map((Stop) => {
return {
Colour: {
R: Stop.color.r,
G: Stop.color.g,
B: Stop.color.b,
},
TimePosition: Stop.position
}
}),
Transparency: Filler.gradientStops.map((Stop) => {
return {
Transparency: 1 - Stop.color.a, // Bastards for using RGBA
TimePosition: Stop.position
}
}),
Rotation: Rotation,
Children: []
});
break;
case "IMAGE":
if (Properties.Class == "Frame") {
console.warn("Images are not supported, however an ImageLabel has been created.");
Properties.Image = "rbxasset://textures/StudioConvertToPackagePlugin/placeholder.png"
Properties.BackgroundColor3 = Properties.BackgroundColor3 || {R: 1, G: 0, B: 1};
Properties.Class = "ImageLabel";
}
break;
default:
return QuickClose(`Unsupported fill type '${Filler.type}' for: ${Element.name}`);
}
},
["cornerRadius"]: (Element, Properties) => {
if (Element.cornerRadius != 0) {
Properties.Children.push({
Class: "UICorner",
Type: "UICorner",
CornerRadius: {
S: 0,
O: Element.cornerRadius,
},
Children: []
});
}
},
["fontName"]: (Element, Properties) => {
const UsedFonts = Element.getStyledTextSegments(["fontName", "fontSize", "fontWeight", "fills"]);
if (UsedFonts.length === 1) {
Properties.Font = {
Family: Element.fontName.family,
Style: Element.fontName.style
}
return;
}
Properties.RichText = true;
var PreviousFont;
var NewText = "";
function IsMulti(Font, property) {
if (Element[property] !== figma.mixed || Element[property] === undefined || Element[property] === Font[property] || Font[property] === undefined) {
return false;
}
return true;
}
//const Font = Fonts[Properties.Font.Style] || Fonts["Regular"];
//ExtendXML(`<Font name="FontFace"><Family><url>rbxasset://fonts/families/${Properties.Font.Family}.json</url></Family><Weight>${Font.Weight}</Weight><Style>${Font.Style}</Style></Font>`);
function Check(Font) {
const RblxFont = (Font.fontName && Fonts[Font.fontName.style]) || Fonts["Regular"];
var NextTextSegment = "";
var ExtraSegments = "";
if (IsMulti(Font, "fontSize")) {
NextTextSegment += `size="${Font.fontSize}" `;
}
if (IsMulti(Font, "fontName") && Font.fontName.family) {
NextTextSegment += `family="rbxasset://fonts/families/${Font.fontName.family}.json" `;
}
if (IsMulti(Font, "fontName") && Font.fontName.style) {
NextTextSegment += `style="${RblxFont.Style}" `;
}
if (IsMulti(Font, "fontWeight")) {
NextTextSegment += `weight="${RblxFont.Weight}" `;
}
if (IsMulti(Font, "fills") && Font.fills.length > 0) {
if (Font.fills[0].type !== "SOLID") {
console.warn("Gradient text is not supported.");
} else {
var Colour = Font.fills[0].color;
NextTextSegment += `color="rgb(${LimitDecimals(Colour.r * 255)},${LimitDecimals(Colour.g * 255)},${LimitDecimals(Colour.b * 255)})" `;
NextTextSegment += `transparency="${1 - Font.fills[0].opacity}"`
}
}
// End Segments
// Special Segments
if (IsMulti(Font, "textDecoration")) {
switch (Font.textDecoration) {
case "UNDERLINE":
ExtraSegments += `<u>`;
break;
case "STRIKETHROUGH":
ExtraSegments += `<s>`;
break;
}
}
// End Special Segments
if (NextTextSegment.length > 0) NewText += `<font ${NextTextSegment}>`
NewText += ExtraSegments + `${Font.characters}` + ExtraSegments.replace("<", "</");
if (NextTextSegment.length > 0) NewText += `</font>`
}
for (var i = 0; i < UsedFonts.length; i++) {
const Font = UsedFonts[i];
Check(Font);
}
Properties.Text = "<![CDATA[" + NewText + "]]>";
},
["strokes"]: (Element, Properties) => {
if (Element.strokes.length == 0) {
return;
}
const Stroke = Element.strokes[0];
if ((Stroke.type !== "SOLID" && Stroke.type !== "GRADIENT_LINEAR") || Stroke.visible === false) return;
if (Stroke.type === "GRADIENT_LINEAR") {
const Transform = Stroke.gradientTransform;
const Rotation = getGradientRotation(Transform);
Properties.Children.push({
Class: "UIStroke",
Type: "UIStroke",
Colour: {
R: 1,
G: 1,
B: 1,
},
Transparency: Element.opacity,
Thickness: Element.strokeWeight,
LineJoinMode: Element.strokeJoin.substring(0, 1).toUpperCase() + Element.strokeJoin.substring(1).toLowerCase(),
Children: [{
Class: "UIGradient",
Type: "UIGradient",
Transparency: 1 - Stroke.opacity,
Enabled: Stroke.visible,
Colour: Stroke.gradientStops.map((Stop) => {
return {
Colour: {
R: Stop.color.r,
G: Stop.color.g,
B: Stop.color.b,
},
TimePosition: Stop.position
}
}),
Transparency: Stroke.gradientStops.map((Stop) => {
return {
Transparency: 1 - Stop.color.a, // Bastards for using RGBA
TimePosition: Stop.position
}
}),
Rotation: Rotation,
Children: []
}]
});
return;
}
Properties.Children.push({
Class: "UIStroke",
Type: "UIStroke",
Colour: {
R: Stroke.color.r || 1,
G: Stroke.color.g || 1,
B: Stroke.color.b || 1,
},
Transparency: Element.opacity,
Thickness: Element.strokeWeight,
LineJoinMode: Element.strokeJoin.substring(0, 1).toUpperCase() + Element.strokeJoin.substring(1).toLowerCase(),
Children: []
});
}
}
const ElementTypes = {
["GROUP"]: (Element, Parent) => {
var Properties = {
Class: "Frame",
Type: Element.type,
Name: Element.name,
BackgroundTransparency: 0,
BorderSizePixel: 0,
GroupOpacity: Element.opacity,
Visible: Element.visible,
Position: {
X: Element.x,
Y: Element.y
},
_OriginalPosition: {
X: Element.x,
Y: Element.y
},
Size: {
X: Element.width,
Y: Element.height
},
Children: [],
Parent: Parent,
}
if (Parent !== undefined) {
Properties.Position.X -= Parent._OriginalPosition.X;
Properties.Position.Y -= Parent._OriginalPosition.Y;
Properties.GroupOpacity = Parent.GroupOpacity * Properties.GroupOpacity; // maths :)
}
for (const Property in Element) {
if (Property in PropertyTypes) {
if (PropertyTypes[Property](Element, Properties) === false) return false;
}
}
return Properties;
},
["RECTANGLE"]: (Element, Parent) => {
var Properties = {
Class: "Frame",
Type: Element.type,
Name: Element.name,
BackgroundTransparency: Element.opacity,
BorderSizePixel: 0,
Visible: Element.visible,
Position: {
X: Element.x,
Y: Element.y
},
Size: {
X: Element.width,
Y: Element.height
},
Rotation: -Element.rotation,
Children: [],
Parent: Parent,
}
if (Parent !== undefined) {
Properties.Position.X -= Parent._OriginalPosition.X;
Properties.Position.Y -= Parent._OriginalPosition.Y;
Properties.BackgroundTransparency = Parent.GroupOpacity * Properties.BackgroundTransparency; // maths :)
}
for (const Property in Element) {
if (Property in PropertyTypes) {
if (PropertyTypes[Property](Element, Properties) === false) return false;
}
}
return Properties;
},
["LINE"]: (Element, Parent) => {
var Properties = {
Class: "Frame",
Type: Element.type,
Name: Element.name,
BackgroundTransparency: Element.opacity,
BorderSizePixel: 0,
Visible: Element.visible,
Position: {
X: Element.x,
Y: Element.y
},
Size: {
X: Element.width,
Y: 0
},
Rotation: -Element.rotation,
Children: [],
Parent: Parent,
}
if (Parent !== undefined) {
Properties.Position.X -= Parent._OriginalPosition.X;
Properties.Position.Y -= Parent._OriginalPosition.Y;
//Properties.BackgroundTransparency = Parent.GroupOpacity * Properties.BackgroundTransparency; // maths :)
}
for (const Property in Element) {
if (Property in PropertyTypes) {
if (PropertyTypes[Property](Element, Properties) === false) return false;
}
}
return Properties;
},
["ELLIPSE"]: (Element, Parent) => {
var Properties = {
Class: "Frame",
Type: Element.type,
Name: Element.name,
BackgroundTransparency: Element.opacity,
BorderSizePixel: 0,
Visible: Element.visible,
Position: {
X: Element.x,
Y: Element.y
},
Size: {
X: Element.width,
Y: Element.height
},
Rotation: Element.rotation,
Children: [
{
Class: "UICorner",
Type: "UICorner",
CornerRadius: {
S: 1,
O: 0,
}
}
],
Parent: Parent,
}
if (Parent !== undefined) {
Properties.Position.X -= Parent._OriginalPosition.X;
Properties.Position.Y -= Parent._OriginalPosition.Y;
Properties.BackgroundTransparency = Parent.GroupOpacity * Properties.BackgroundTransparency; // maths :)
}
for (const Property in Element) {
if (Property in PropertyTypes) {
if (PropertyTypes[Property](Element, Properties) === false) return false;
}
}
return Properties;
},
["TEXT"]: (Element, Parent) => {
/*
=== WARNING ===
Some fonts are not supported by Roblox.
If you don't see the text in Roblox, check the following:
Check the output for a message saying "Temp read failed"
if FontFace says "Temp read failed."" This means the font is not supported. (in a red boarder)
if FontFace/Weight says "(unavailable)" This means the font style is not supported.
*/
var Properties = {
Class: "TextLabel",
Type: Element.type,
Name: Element.name,
BackgroundTransparency: 0,
BorderSizePixel: 0,
TextTransparency: Element.opacity,
Visible: Element.visible,
Position: {
X: Element.x,
Y: Element.y
},
Size: {
X: Element.width,
Y: Element.height
},
TextSize: Element.fontSize,
TextXAlignment: Element.textAlignHorizontal,
TextYAlignment: Element.textAlignVertical,
Text: Element.characters,
Rotation: Element.rotation,
Children: [],
Parent: Parent,
}
if (Parent !== undefined) {
Properties.Position.X -= Parent._OriginalPosition.X;
Properties.Position.Y -= Parent._OriginalPosition.Y;
Properties.TextTransparency = Parent.GroupOpacity * Properties.TextTransparency; // maths :)
}
for (const Property in Element) {
if (Property in PropertyTypes) {
if (PropertyTypes[Property](Element, Properties) === false) return false;
}
}
return Properties;
}
}
// ^^ Couldn't get this to work, so I just copied the code from Conversions.js
function CreateRobloxElement(Properties) { // Creates the roblox xml for the element
var XML = "";
//var Count = 0;
function ExtendXML(String) {
//Count += 1;
XML += String;
}
ExtendXML(`<Item class="${Properties.Class}" referent="RBX0">`);
ExtendXML(`<Properties>`);
// Add properties
ExtendXML(`<string name="Name">${(Properties.Name || Properties.Class).replace("\n", "")}</string>`);
if (Properties.BackgroundColor3 !== undefined) {
var Colour = Properties.BackgroundColor3;
for (const Property in Colour) {
Colour[Property] = LimitDecimals(Colour[Property], 6);
}
ExtendXML(`<Color3 name="BackgroundColor3"><R>${Colour.R}</R><G>${Colour.G}</G><B>${Colour.B}</B></Color3>`);
}
if (Properties.TextColor3 !== undefined) {
var Colour = Properties.TextColor3;
for (const Property in Colour) {
Colour[Property] = LimitDecimals(Colour[Property], 6);
}
ExtendXML(`<Color3 name="TextColor3"><R>${Colour.R}</R><G>${Colour.G}</G><B>${Colour.B}</B></Color3>`);
}
if (Properties.Colour !== undefined) {
var Colour = Properties.Colour;
if (Colour["R"] !== undefined) {
for (const Property in Colour) {
Colour[Property] = LimitDecimals(Colour[Property], 6);
}
ExtendXML(`<Color3 name="Color"><R>${Colour.R}</R><G>${Colour.G}</G><B>${Colour.B}</B></Color3>`);
} else {
var ColourSeq = "";
var Previous;
for (var i = 0; i < Colour.length; i++) {
const ColourStop = Colour[i];
var ColourVal = ColourStop.Colour;
for (const Property in ColourVal) {
ColourVal[Property] = LimitDecimals(ColourVal[Property], 6);
}
Previous = ColourStop;
ColourSeq += `${ColourStop.TimePosition} ${ColourVal.R} ${ColourVal.G} ${ColourVal.B} 0 `;
}
if (Previous.TimePosition !== 1) ColourSeq += `1 ${Previous.Colour.R} ${Previous.Colour.G} ${Previous.Colour.B} 0 `; // Add the last keyframe (if it doesn't exist)
ExtendXML(`<ColorSequence name="Color">${ColourSeq}</ColorSequence>`);
}
}
if (Properties.Transparency !== undefined) {
const Transparency = Properties.Transparency;
if (!Array.isArray(Transparency)) ExtendXML(`<float name="Transparency">${1 - LimitDecimals(Properties.Transparency, 3)}</float>`);
else {
var NumberSequence = "";
var Previous;
for (var i = 0; i < Transparency.length; i++) {
var TransparencyStop = Transparency[i];
Previous = TransparencyStop;
NumberSequence += `${TransparencyStop.TimePosition} ${LimitDecimals(TransparencyStop.Transparency, 3)} 0 `;
}
if (Previous.TimePosition !== 1) NumberSequence += `1 ${Previous.Transparency} 0 `; // Add the last keyframe (if it doesn't exist)
ExtendXML(`<NumberSequence name="Transparency">${NumberSequence}</NumberSequence>`);
}
}
/*if (Properties.Position !== undefined && Properties.Size !== undefined && Properties.Rotation !== undefined && Properties.Rotation !== 0) {
var Position = Properties.Position;
var Size = Properties.Size;
var Center = {
X: Position.X + Size.X / 2,
Y: Position.Y + Size.Y / 2
}
var Radians = Properties.Rotation * Math.PI / 180;
var RotatedCenter;
if (Properties.Rotation < 0) {
RotatedCenter = {
X: Center.X * Math.cos(Radians) + Center.Y * Math.sin(Radians),
Y: Center.X * Math.sin(Radians) - Center.Y * Math.cos(Radians) + Size.Y
}
} else {
RotatedCenter = {
X: Center.X * Math.cos(Radians) + Center.Y * Math.sin(Radians),
Y: -Center.X * Math.sin(Radians) + Center.Y * Math.cos(Radians)
}
}
var RotatedPosition = {
X: LimitDecimals(RotatedCenter.X - Size.X / 2, 0),
Y: LimitDecimals(-RotatedCenter.Y - Size.Y / 2, 0)
}
ExtendXML(`<UDim2 name="Position"><XS>0</XS><XO>${RotatedPosition.X}</XO><YS>0</YS><YO>${RotatedPosition.Y}</YO></UDim2>`);
ExtendXML(`<UDim2 name="Size"><XS>0</XS><XO>${LimitDecimals(Size.X, 0)}</XO><YS>0</YS><YO>${LimitDecimals(Size.Y, 0)}</YO></UDim2>`);
ExtendXML(`<float name="Rotation">${LimitDecimals(-Properties.Rotation, 3)}</float>`);
} else {*/
if (Properties.Position !== undefined) {
var Position = Properties.Position;
ExtendXML(`<UDim2 name="Position"><XS>0</XS><XO>${LimitDecimals(Position.X, 0)}</XO><YS>0</YS><YO>${LimitDecimals(Position.Y, 0)}</YO></UDim2>`);
}
if (Properties.Size !== undefined) {
var Size = Properties.Size;
ExtendXML(`<UDim2 name="Size"><XS>0</XS><XO>${LimitDecimals(Size.X, 0)}</XO><YS>0</YS><YO>${LimitDecimals(Size.Y, 0)}</YO></UDim2>`);
}
if (Properties.Rotation !== undefined) {
if (Properties.Class !== "UIGradient") {
console.warn("WARNING: Rotation support has been temporarily removed as it will result in unexpected behaviour. You will need to manually rotate the object in Roblox Studio.");
}
else ExtendXML(`<float name="Rotation">${LimitDecimals(Properties.Rotation, 3)}</float>`); // Rotation is supported for UIGradient
}
if (Properties.BackgroundTransparency !== undefined) ExtendXML(`<float name="BackgroundTransparency">${1 - LimitDecimals(Properties.BackgroundTransparency, 3)}</float>`);
if (Properties.Thickness !== undefined) ExtendXML(`<float name="Thickness">${LimitDecimals(Properties.Thickness, 0)}</float>`);
if (Properties.LineJoinMode !== undefined) ExtendXML(`<Enum name="LineJoinMode">${LineJoinModes.indexOf(Properties.LineJoinMode)}</Enum>`);
if (Properties.CornerRadius !== undefined) ExtendXML(`<UDim2 name="CornerRadius"><S>${LimitDecimals(Properties.CornerRadius.S, 0)}</S><O>${LimitDecimals(Properties.CornerRadius.O, 0)}</O></UDim2>`);
if (Properties.BorderSizePixel !== undefined) ExtendXML(`<int name="BorderSizePixel">${LimitDecimals(Properties.BorderSizePixel, 0)}</int>`);
if (Properties.ClipsDescendants !== undefined) ExtendXML(`<bool name="ClipsDescendants">${Properties.ClipsDescendants}</bool>`);
if (Properties.TextTransparency !== undefined) ExtendXML(`<float name="TextTransparency">${1 - Properties.TextTransparency}</float>`);
if (Properties.TextSize !== undefined) ExtendXML(`<int name="TextSize">${LimitDecimals(Properties.TextSize, 0)}</int>`);
if (Properties.Text !== undefined) ExtendXML(`<string name="Text">${Properties.Text}</string>`);
if (Properties.TextWrapped !== undefined) ExtendXML(`<bool name="TextWrapped">${Properties.TextWrapped}</bool>`);
if (Properties.TextScaled !== undefined) ExtendXML(`<bool name="TextScaled">${Properties.TextScaled}</bool>`);
if (Properties.TextStrokeTransparency !== undefined) ExtendXML(`<float name="TextStrokeTransparency">${1 - Properties.TextStrokeTransparency}</float>`);
if (Properties.TextStrokeColor3 !== undefined) ExtendXML(`<Color3 name="TextStrokeColor3"><R>${LimitDecimals(Properties.TextStrokeColor3.R, 3)}</R><G>${LimitDecimals(Properties.TextStrokeColor3.G, 3)}</G><B>${LimitDecimals(Properties.TextStrokeColor3.B, 3)}</B></Color3>`);
if (Properties.TextXAlignment !== undefined) ExtendXML(`<token name="TextXAlignment">${TextXAlignments.indexOf(Properties.TextXAlignment)}</token>`);
if (Properties.TextYAlignment !== undefined) ExtendXML(`<token name="TextYAlignment">${TextYAlignments.indexOf(Properties.TextYAlignment)}</token>`);
if (Properties.Font !== undefined) {
const Font = Fonts[Properties.Font.Style] || Fonts["Regular"];
ExtendXML(`<Font name="FontFace"><Family><url>rbxasset://fonts/families/${Properties.Font.Family}.json</url></Family><Weight>${Font.Weight}</Weight><Style>${Font.Style}</Style></Font>`);
}
if (Properties.RichText !== undefined) ExtendXML(`<bool name="RichText">${Properties.RichText}</bool>`);
if (Properties.Image !== undefined) ExtendXML(`<string name="Image">${Properties.Image}</string>`);
//if (Properties.Position !== undefined) ExtendXML(`<UDim2 name="Position"><XS>0</XS><XO>${LimitDecimals(Properties.Position.X, 0)}</XO><YS>0</YS><YO>${LimitDecimals(Properties.Position.Y, 0)}</YO></UDim2>`);
//if (Properties.Size !== undefined) ExtendXML(`<UDim2 name="Size"><XS>0</XS><XO>${LimitDecimals(Properties.Size.X, 0)}</XO><YS>0</YS><YO>${LimitDecimals(Properties.Size.Y, 0)}</YO></UDim2>`);
//if (Properties.Rotation !== undefined) ExtendXML(`<float name="Rotation">${LimitDecimals(Properties.Rotation, 3)}</float>`);
if (Properties.Visible !== undefined) ExtendXML(`<bool name="Visible">${Properties.Visible}</bool>`);
if (Properties.Enabled !== undefined) ExtendXML(`<bool name="Enabled">${Properties.Enabled}</bool>`);
// Check if everything was converted
/*var CC = -2; // Inclde Children & Type
for (var _ in Properties) {
CC += 1;
}
if (CC !== Count) {
console.warn("WARNING: Some properties were not converted. Please report this to NoTwistedHere#6703.");
console.log(`READ: ${Count}, EXPECTED: ${CC}\nPROPS:`);
console.log(Properties);
}*/
// End of properties
ExtendXML("</Properties>");
// Add children
if (Properties.Children !== undefined && Properties.Children.length > 0) {
for (var i = 0; i < Properties.Children.length; i++) {
ExtendXML(CreateRobloxElement(Properties.Children[i], i));
}
}
return XML + "</Item>";
}
function ConvertToRoblox(Objects) { // Converts the code into roblox xml format
var XML = '<!--\n\tGenerated by Figma to Roblox\n\tReport any bugs/issues to NoTwistedHere#6703\n-->\n\n<roblox xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.roblox.com/roblox.xsd" version="4"><Meta name="ExplicitAutoJoints">true</Meta>';
for (var i = 0; i < Objects.length; i++) {
XML += CreateRobloxElement(Objects[i]);
}
return XML + '</roblox>';
}
function GetMainProperties(Object, Parent) {
if (ElementTypes[Object.type] !== undefined) {
return ElementTypes[Object.type](Object, Parent);
}
return QuickClose(`Unsupported element type '${Object.type}' for: ${Object.name}`);
}
function RunPlugin() {
// Get selected elements
var SelectedElements = figma.currentPage.selection;
if (SelectedElements.length == 0) {
return QuickClose("No elements selected");
}
//console.log(SelectedElements);
// Get main properties
Notify("Converting...")
var Objects = [];
for (var i = 0; i < SelectedElements.length; i++) {
Objects.push(GetMainProperties(SelectedElements[i]));
}
Notify("Formatting...");
var XML = ConvertToRoblox(Objects);
Objects = null;
if (XML === false) {
return;
}
//console.log("=== START OF XML ===")
//console.log(XML);
//console.log("=== END OF XML ===")
figma.ui.postMessage({
type: "Download",
data: XML
})
XML = null;
Notify("Successfully converted!");
}
try {
//RunPlugin();
figma.showUI(__html__);
figma.ui.onmessage = msg => {
console.log(msg);
switch (msg.type) {
case "exec":
try {
RunPlugin();
} catch (e) {
if (!HandledError) {
throw e;
}
console.warn(e);
}
break;
case "close-plugin":
figma.closePlugin();
break;
}
}
} catch (e) {
if (!HandledError) {
throw e;
}
console.warn(e);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment