Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
// NCCode.cpp
/*
* Copyright (c) 2009, Dan Heeks
* This program is released under the BSD license. See the file COPYING for
* details.
*/
#include <stdafx.h>
#include <math.h>
#include "NCCode.h"
#include "strconv.h"
#include "Picking.h"
#include "HeeksFont.h"
#include "GripData.h"
#include <memory>
#include <sstream>
double TOLERANCE = 1.0e-06;
int CNCCode::s_arc_interpolation_count = 20;
int CNCCode::m_type = 0;
int CNCCodeBlock::m_type = 0;
void ColouredText::WriteXML(TiXmlNode *root)
{
TiXmlElement * element;
element = new TiXmlElement( "text" );
root->LinkEndChild(element);
// add actual text as a child object
TiXmlText* text = new TiXmlText(Ttc(m_str.c_str()));
element->LinkEndChild(text);
if(m_color_type != ColorDefaultType)element->SetAttribute( "col", CNCCode::GetColor(m_color_type));
}
void ColouredText::ReadFromXMLElement(TiXmlElement* element)
{
m_color_type = CNCCode::GetColor(element->Attribute("col"));
// get the text
const char* text = element->GetText();
if(text)m_str = std::wstring(Ctt(text));
}
void PathObject::WriteBaseXML(TiXmlElement *pElem)
{
pElem->SetDoubleAttribute("x", m_point.x);
pElem->SetDoubleAttribute("y", m_point.y);
pElem->SetDoubleAttribute("z", m_point.z);
} // End WriteXML() method
void PathObject::ReadFromXMLElement(TiXmlElement* pElem)
{
double x;
m_point = CNCCode::prev_point;
if(pElem->Attribute("x", &x))m_point.x = x * CNCCodeBlock::multiplier;
if(pElem->Attribute("y", &x))m_point.y = x * CNCCodeBlock::multiplier;
if(pElem->Attribute("z", &x))m_point.z = x * CNCCodeBlock::multiplier;
CNCCode::prev_point = m_point;
}
void PathLine::WriteXML(TiXmlNode *root)
{
TiXmlElement * element;
element = new TiXmlElement( "line" );
root->LinkEndChild(element);
PathObject::WriteBaseXML(element);
}
void PathLine::ReadFromXMLElement(TiXmlElement* pElem)
{
PathObject::ReadFromXMLElement(pElem);
}
void PathLine::glVertices()
{
if(CNCCode::prev_point_valid)glVertex3dv(CNCCode::prev_point.getBuffer());
glVertex3dv(m_point.getBuffer());
}
void PathArc::GetBox(CBox &box)
{
if (CNCCode::prev_point_valid)
{
double dist = Point(m_point.x, m_point.y).dist(Point(CNCCode::prev_point.x, CNCCode::prev_point.y));
if (dist > 1.0e-06)
{
double radius = 0.0;
if (IsIncluded(Point3d(0, 1, 0), radius))
box.Insert(CNCCode::prev_point.x + m_c.x, CNCCode::prev_point.y + m_c.y + radius, 0);
if (IsIncluded(Point3d(0, -1, 0), radius))
box.Insert(CNCCode::prev_point.x + m_c.x, CNCCode::prev_point.y + m_c.y - radius, 0);
if (IsIncluded(Point3d(1, 0, 0), radius))
box.Insert(CNCCode::prev_point.x + m_c.x + radius, CNCCode::prev_point.y + m_c.y, 0);
if (IsIncluded(Point3d(-1, 0, 0), radius))
box.Insert(CNCCode::prev_point.x + m_c.x - radius, CNCCode::prev_point.y + m_c.y, 0);
}
}
PathObject::GetBox(box);
}
bool PathArc::IsIncluded(const Point3d &pnt, double &radius)
{
double sx = -m_c.x;
double sy = -m_c.y;
// e = cs + se = -c + e - s
double ex = -m_c.x + m_point.x - CNCCode::prev_point.x;
double ey = -m_c.y + m_point.y - CNCCode::prev_point.y;
radius = sqrt(sx * sx + sy * sy);
double start_angle = atan2(sy, sx);
double end_angle = atan2(ey, ex);
if(m_dir == 1){
if(end_angle < start_angle)end_angle += 6.283185307179;
}
else{
if(start_angle < end_angle)start_angle += 6.283185307179;
}
// double angle_step = 0;
if (start_angle == end_angle)
// It's a full circle.
return true;
double the_angle = atan2(pnt.y, pnt.x);
double the_angle2 = the_angle + 2*PI;
return (the_angle >= start_angle && the_angle <= end_angle) || (the_angle2 >= start_angle && the_angle2 <= end_angle);
}
void PathArc::WriteXML(TiXmlNode *root)
{
TiXmlElement * element = new TiXmlElement( "arc" );
root->LinkEndChild(element);
element->SetDoubleAttribute( "i", m_c.x);
element->SetDoubleAttribute( "j", m_c.y);
element->SetDoubleAttribute( "k", m_c.z);
element->SetDoubleAttribute( "d", m_dir);
PathObject::WriteBaseXML(element);
}
void PathArc::ReadFromXMLElement(TiXmlElement* pElem)
{
// get the attributes
if (pElem->Attribute("i")) pElem->Attribute("i", &m_c.x);
if (pElem->Attribute("j")) pElem->Attribute("j", &m_c.y);
if (pElem->Attribute("k")) pElem->Attribute("k", &m_c.z);
if (pElem->Attribute("d")) pElem->Attribute("d", &m_dir);
m_c.x *= CNCCodeBlock::multiplier;
m_c.y *= CNCCodeBlock::multiplier;
m_c.z *= CNCCodeBlock::multiplier;
PathObject::ReadFromXMLElement(pElem);
}
void PathArc::glVertices()
{
if (!CNCCode::prev_point_valid) return;
std::list<Point3d> vertices;
Interpolate( CNCCode::s_arc_interpolation_count, vertices);
glVertex3dv(CNCCode::prev_point.getBuffer());
for (std::list<Point3d>::const_iterator l_itVertex = vertices.begin(); l_itVertex != vertices.end(); l_itVertex++)
{
glVertex3dv(l_itVertex->getBuffer());
} // End for
}
void PathArc::Interpolate(const unsigned int number_of_points, std::list<Point3d>& points ) const
{
if (Point(m_point.x, m_point.y) == Point(CNCCode::prev_point.x, CNCCode::prev_point.y))
return;
double sx = -m_c.x;
double sy = -m_c.y;
// e = cs + se = -c + e - s
double ex = -m_c.x + m_point.x - CNCCode::prev_point.x;
double ey = -m_c.y + m_point.y - CNCCode::prev_point.y;
double rs = sqrt(sx * sx + sy * sy);
double re = sqrt(ex * ex + ey * ey);
double start_angle = atan2(sy, sx);
double end_angle = atan2(ey, ex);
if(m_dir == 1){
if(end_angle < start_angle)end_angle += 6.283185307179;
}
else{
if(start_angle < end_angle)start_angle += 6.283185307179;
}
double angle_step = 0;
if (start_angle == end_angle)
{
// It's a full circle.
angle_step = (2 * PI) / number_of_points;
if (m_dir == -1)
{
angle_step = -angle_step; // fix preview of full cw arcs
}
} // End if - then
else
{
// It's an arc.
angle_step = (end_angle - start_angle) / number_of_points;
} // End if - else
points.push_back(CNCCode::prev_point);
for(unsigned int i = 0; i< number_of_points; i++)
{
double angle = start_angle + angle_step * (i + 1);
double r = rs + ((re - rs) * (i + 1)) /number_of_points;
double x = CNCCode::prev_point.x + m_c.x + r * cos(angle);
double y = CNCCode::prev_point.y + m_c.y + r * sin(angle);
double z = CNCCode::prev_point.z + ((m_point.z - CNCCode::prev_point.z) * (i + 1)) / number_of_points;
points.push_back( Point3d( x, y, z ) );
}
}
ColouredPath::ColouredPath(const ColouredPath& c)
{
operator=(c);
}
const ColouredPath &ColouredPath::operator=(const ColouredPath& c)
{
Clear();
m_color_type = c.m_color_type;
for(std::list< PathObject* >::const_iterator It = c.m_points.begin(); It != c.m_points.end(); It++)
{
PathObject* object = *It;
m_points.push_back(object->MakeACopy());
}
return *this;
}
void ColouredPath::Clear()
{
for(std::list< PathObject* >::iterator It = m_points.begin(); It != m_points.end(); It++)
{
PathObject* object = *It;
delete object;
}
m_points.clear();
}
void ColouredPath::glCommands(bool no_color)
{
if(!no_color)CNCCode::Color(m_color_type).glColor();
glBegin(GL_LINE_STRIP);
for(std::list< PathObject* >::iterator It = m_points.begin(); It != m_points.end(); It++)
{
PathObject* po = *It;
po->glVertices();
CNCCode::prev_point = po->m_point;
CNCCode::prev_point_valid = true;
}
glEnd();
}
void ColouredPath::GetBox(CBox &box)
{
for(std::list< PathObject* >::iterator It = m_points.begin(); It != m_points.end(); It++)
{
PathObject* po= *It;
po->GetBox(box);
CNCCode::prev_point = po->m_point;
CNCCode::prev_point_valid = true;
}
}
void ColouredPath::WriteXML(TiXmlNode *root)
{
TiXmlElement * element;
element = new TiXmlElement( "path" );
root->LinkEndChild(element);
element->SetAttribute( "col", CNCCode::GetColor(m_color_type));
for(std::list< PathObject* >::iterator It = m_points.begin(); It != m_points.end(); It++)
{
PathObject* po = *It;
po->WriteXML(element);
}
}
void ColouredPath::ReadFromXMLElement(TiXmlElement* element)
{
// get the attributes
m_color_type = CNCCode::GetColor(element->Attribute("col"), ColorRapidType);
// loop through all the objects
for(TiXmlElement* pElem = TiXmlHandle(element).FirstChildElement().Element() ; pElem; pElem = pElem->NextSiblingElement())
{
std::string name(pElem->Value());
if(name == "line")
{
PathLine *new_object = new PathLine;
new_object->ReadFromXMLElement(pElem);
m_points.push_back(new_object);
}
else if(name == "arc")
{
PathArc *new_object = new PathArc;
new_object->ReadFromXMLElement(pElem);
m_points.push_back(new_object);
}
}
}
double CNCCodeBlock::multiplier = 1.0;
HeeksObj *CNCCodeBlock::MakeACopy(void)const{return new CNCCodeBlock(*this);}
void CNCCodeBlock::glCommands(bool select, bool marked, bool no_color)
{
if(marked)glLineWidth(3);
for(std::list<ColouredPath>::iterator It = m_line_strips.begin(); It != m_line_strips.end(); It++)
{
ColouredPath& line_strip = *It;
line_strip.glCommands(no_color);
}
if(marked)glLineWidth(1);
}
void CNCCodeBlock::GetBox(CBox &box)
{
for(std::list<ColouredPath>::iterator It = m_line_strips.begin(); It != m_line_strips.end(); It++)
{
ColouredPath& line_strip = *It;
line_strip.GetBox(box);
}
}
void CNCCodeBlock::WriteToXML(TiXmlElement *element)
{
for(std::list<ColouredText>::iterator It = m_text.begin(); It != m_text.end(); It++)
{
ColouredText &text = *It;
text.WriteXML(element);
}
for(std::list<ColouredPath>::iterator It = m_line_strips.begin(); It != m_line_strips.end(); It++)
{
ColouredPath &line_strip = *It;
line_strip.WriteXML(element);
}
element->SetAttribute("tool_number", m_tool_number);
HeeksObj::WriteToXML(element);
}
void CNCCodeBlock::ReadFromXML(TiXmlElement* element)
{
m_from_pos = CNCCode::pos;
// loop through all the objects
for(TiXmlElement* pElem = TiXmlHandle(element).FirstChildElement().Element() ; pElem; pElem = pElem->NextSiblingElement())
{
std::string name(pElem->Value());
if(name == "text")
{
ColouredText t;
t.ReadFromXMLElement(pElem);
m_text.push_back(t);
CNCCode::pos += t.m_str.length();
}
else if(name == "path")
{
ColouredPath l;
l.ReadFromXMLElement(pElem);
m_line_strips.push_back(l);
}
else if(name == "mode")
{
const char* units = pElem->Attribute("units");
if(units)pElem->Attribute("units", &CNCCodeBlock::multiplier);
}
}
if(m_text.size() > 0)CNCCode::pos++;
m_to_pos = CNCCode::pos;
element->Attribute("tool_number", &m_tool_number);
HeeksObj::ReadFromXML(element);
}
void CNCCodeBlock::AppendText(std::wstring& str)
{
if(m_text.size() == 0)return;
for(std::list<ColouredText>::iterator It = m_text.begin(); It != m_text.end(); It++)
{
ColouredText &text = *It;
str.append(text.m_str);
}
str.append(L"\n");
}
void CNCCodeBlock::GetGripperPositionsTransformed(std::list<GripData> *list, bool just_for_endof)
{
for (std::list<ColouredPath>::iterator It = m_line_strips.begin(); It != m_line_strips.end(); It++)
{
ColouredPath& line_strip = *It;
for (std::list< PathObject* >::const_iterator It = line_strip.m_points.begin(); It != line_strip.m_points.end(); It++)
{
PathObject* object = *It;
list->push_back(GripData(GripperTypeTranslate, object->m_point, NULL));
}
}
}
void CNCCodeBlock::GetProperties(std::list<Property *> *list)
{
}
long CNCCode::pos = 0;
// static
bool CNCCode::prev_point_valid = false;
Point3d CNCCode::prev_point = Point3d(0,0,0);
std::map<std::string,ColorEnum> CNCCode::m_colors_s_i;
std::map<ColorEnum,std::string> CNCCode::m_colors_i_s;
std::vector<HeeksColor> CNCCode::m_colors;
const wchar_t* CNCCode::GetIconFilePath()
{
static std::wstring iconpath = cam_dir + L"/icons/nccode.png";
return iconpath.c_str();
}
void CNCCode::ClearColors(void)
{
CNCCode::m_colors_s_i.clear();
CNCCode::m_colors_i_s.clear();
CNCCode::m_colors.clear();
}
void CNCCode::AddColor(const char* name, const HeeksColor& col)
{
ColorEnum i = (ColorEnum)ColorCount();
m_colors_s_i.insert(std::pair<std::string,ColorEnum>(std::string(name), i));
m_colors_i_s.insert(std::pair<ColorEnum,std::string>(i, std::string(name)));
m_colors.push_back(col);
}
ColorEnum CNCCode::GetColor(const char* name, ColorEnum def)
{
if (name == NULL) return def;
std::map<std::string,ColorEnum>::iterator it = m_colors_s_i.find(std::string(name));
if (it != m_colors_s_i.end()) return it->second;
else return def;
}
const char* CNCCode::GetColor(ColorEnum i, const char* def)
{
std::map<ColorEnum,std::string>::iterator it = m_colors_i_s.find(i);
if (it != m_colors_i_s.end()) return it->second.c_str();
else return def;
}
CNCCode::~CNCCode()
{
Clear();
}
const CNCCode &CNCCode::operator=(const CNCCode &rhs)
{
HeeksObj::operator =(rhs);
Clear();
for(std::list<CNCCodeBlock*>::const_iterator It = rhs.m_blocks.begin(); It != rhs.m_blocks.end(); It++)
{
CNCCodeBlock* block = *It;
CNCCodeBlock* new_block = new CNCCodeBlock(*block);
m_blocks.push_back(new_block);
new_block->m_owner = this;
}
return *this;
}
void CNCCode::Clear()
{
for(std::list<CNCCodeBlock*>::iterator It = m_blocks.begin(); It != m_blocks.end(); It++)
{
CNCCodeBlock* block = *It;
delete block;
}
m_blocks.clear();
DestroyGLLists();
m_box = CBox();
m_highlighted_block = NULL;
}
void CNCCode::glCommands(bool select, bool marked, bool no_color)
{
int* plist = select ? &m_select_gl_list : &m_gl_list;
if (*plist)
{
glCallList(*plist);
}
else{
*plist = glGenLists(1);
glNewList(*plist, GL_COMPILE_AND_EXECUTE);
// render all the blocks
CNCCode::prev_point_valid = false;
for(std::list<CNCCodeBlock*>::iterator It = m_blocks.begin(); It != m_blocks.end(); It++)
{
CNCCodeBlock* block = *It;
if(select)SetPickingColor(block->GetIndex());
block->glCommands(select, block == m_highlighted_block, no_color);
}
glEndList();
}
}
void CNCCode::GetBox(CBox &box)
{
if(!m_box.m_valid)
{
CNCCode::prev_point_valid = false;
for(std::list<CNCCodeBlock*>::iterator It = m_blocks.begin(); It != m_blocks.end(); It++)
{
CNCCodeBlock* block = *It;
block->GetBox(m_box);
}
}
box.Insert(m_box);
}
void CNCCode::GetProperties(std::list<Property *> *list)
{
#if 0 // to do
list->push_back(new PropertyInt(_("Arc Interpolation Count"), CNCCode::s_arc_interpolation_count, this, on_set_arc_interpolation_count));
#endif
HeeksObj::GetProperties(list);
}
HeeksObj *CNCCode::MakeACopy(void)const{return new CNCCode(*this);}
void CNCCode::CopyFrom(const HeeksObj* object){operator=(*((CNCCode*)object));}
void CNCCode::WriteToXML(TiXmlElement* element)
{
for(std::list<CNCCodeBlock*>::iterator It = m_blocks.begin(); It != m_blocks.end(); It++)
{
CNCCodeBlock* block = *It;
block->WriteXML(element);
}
element->SetAttribute( "edited", m_user_edited ? 1:0);
HeeksObj::WriteToXML(element);
}
bool CNCCode::CanAdd(HeeksObj* object)
{
return ((object != NULL) && (object->GetType() == CNCCodeBlock::m_type));
}
bool CNCCode::CanAddTo(HeeksObj* owner)
{
if (owner == NULL)
return false;
std::wstring type_string(owner->GetTypeString());
lowerCase(type_string);
return type_string.compare(L"program") == 0;
}
#if 0
bool CNCCode::SetClickMarkPoint(MarkedObject* marked_object, const Point3d &ray_start, const Point3d &ray_direction)
{
if(marked_object->m_map.size() > 0)
{
MarkedObject* sub_marked_object = marked_object->m_map.begin()->second;
if(sub_marked_object)
{
if(sub_marked_object->m_map.size() > 0)
{
HeeksObj* object = sub_marked_object->m_map.begin()->first;
if(object && object->GetType() == CNCCodeBlock::m_type)
{
SetHighlightedBlock((CNCCodeBlock*)object);
int from_pos = m_highlighted_block->m_from_pos;
int to_pos = m_highlighted_block->m_to_pos;
DestroyGLLists();
#if 0
//to do
wxGetApp().m_output_canvas->m_textCtrl->ShowPosition(from_pos);
wxGetApp().m_output_canvas->m_textCtrl->SetSelection(from_pos, to_pos);
#endif
return true;
}
}
}
}
return false;
}
#endif
void CNCCode::ReadFromXML(TiXmlElement* element)
{
pos = 0;
CNCCodeBlock::multiplier = 1.0;
CNCCode::prev_point = Point3d(0, 0, 0);
int line_number = 0;
// loop through all the objects
for(TiXmlElement* pElem = TiXmlHandle(element).FirstChildElement().Element() ; pElem; pElem = pElem->NextSiblingElement())
{
std::string name(pElem->Value());
if(name == "ncblock")
{
CNCCodeBlock* new_object = new CNCCodeBlock;
new_object->ReadFromXML(pElem);
new_object->m_line_number = line_number;
m_blocks.push_back(new_object);
new_object->m_owner = this;
line_number++;
}
}
// loop through the attributes
int i;
element->Attribute("edited", &i);
m_user_edited = (i != 0);
HeeksObj::ReadFromXML(element);
// to do
//new_object->SetTextCtrl(wxGetApp().m_output_canvas->m_textCtrl);
}
void CNCCode::DestroyGLLists(void)
{
if (m_gl_list)
{
glDeleteLists(m_gl_list, 1);
m_gl_list = 0;
}
if (m_select_gl_list)
{
glDeleteLists(m_select_gl_list, 1);
m_select_gl_list = 0;
}
}
void CNCCode::HighlightBlock(long pos)
{
SetHighlightedBlock(NULL);
for(std::list<CNCCodeBlock*>::iterator It = m_blocks.begin(); It != m_blocks.end(); It++)
{
CNCCodeBlock* block = *It;
if(pos < block->m_to_pos)
{
SetHighlightedBlock(block);
break;
}
}
DestroyGLLists();
}
void CNCCode::SetHighlightedBlock(CNCCodeBlock* block)
{
// if(m_highlighted_block)m_highlighted_block->FormatText(wxGetApp().m_output_canvas->m_textCtrl, false, true);
m_highlighted_block = block;
if (m_gl_list)
{
glDeleteLists(m_gl_list, 1);
m_gl_list = 0;
}
// if(m_highlighted_block)m_highlighted_block->FormatText(wxGetApp().m_output_canvas->m_textCtrl, true, true);
}
void CNCCodeViewport::Render()
{
glViewport(0, 0, m_w, m_h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDrawBuffer(GL_BACK);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glClearColor(1, 1, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
if (m_nc_code == NULL)
return;
if (m_nc_code->m_blocks.size() == 0)
return;
glOrtho(0.0f, m_w, 0.0f, m_h, 0.0f, 1.0f);
double lines_to_draw = GetLinesPerPage() + 1;
int start_line = (int)m_current_line;
double extra = m_current_line - start_line;
// move up to start, just above top of window
glTranslated(0, (extra - 0.8) * m_pixels_per_line + m_h, 0);
std::list<CNCCodeBlock*>::iterator It = m_nc_code->m_blocks.begin();
std::advance(It, start_line);
glColor3ub(0, 0, 0);
int line_number = start_line;
for (int i = 0; i < lines_to_draw; i++, line_number++)
{
CNCCodeBlock* block = *It;
if (line_number == (int)m_selected_line)
{
// draw a rectangle
glColor3ub(128, 192, 192);
glBegin(GL_QUADS);
double y0 = m_pixels_per_line * -0.1;
double y1 = m_pixels_per_line * 0.9;
glVertex2d(0, y0);
glVertex2d(m_w, y0);
glVertex2d(m_w, y1);
glVertex2d(0, y1);
glEnd();
}
glPushMatrix();
glScaled(m_pixels_per_line * 0.7, m_pixels_per_line * 0.7, 0);
for (std::list<ColouredText>::iterator TextIt = block->m_text.begin(); TextIt != block->m_text.end(); TextIt++)
{
ColouredText &text = *TextIt;
CNCCode::Color(text.m_color_type).glColor();
//DrawHeeksFontStringAntialiased(Ttc(text.m_str.c_str()), 0.1 / m_pixels_per_line, false, true);
DrawHeeksFontString(Ttc(text.m_str.c_str()), false, true);
}
glPopMatrix();
glTranslated(0, -m_pixels_per_line, 0);
It++;
if (It == m_nc_code->m_blocks.end())
break;
}
}
void CNCCodeViewport::SetNcCode(CNCCode *nc_code)
{
m_nc_code = nc_code;
}
void CNCCodeViewport::SelectLine(double line, bool jump_to_line)
{
m_selected_line = line;
if (jump_to_line)
{
m_current_line = line - GetLinesPerPage() * 0.5;
if (m_current_line < 0.0)m_current_line = 0.0;
double last_first_line = m_nc_code->m_blocks.size() - GetLinesPerPage();
if (m_current_line > last_first_line)m_current_line = last_first_line;
}
int i = 1;
for (std::list<CNCCodeBlock*>::iterator It = m_nc_code->m_blocks.begin(); It != m_nc_code->m_blocks.end(); It++, i++)
{
if (i > line)
{
m_nc_code->SetHighlightedBlock(*It);
break;
}
}
}
CNCCodeBlock* CNCCodeViewport::GetBlockAtLine(int line_number)
{
if (m_nc_code == NULL)
return NULL;
if (m_nc_code->m_blocks.size() <= (size_t)line_number)
return NULL;
std::list<CNCCodeBlock*>::iterator It = m_nc_code->m_blocks.begin();
std::advance(It, line_number);
return *It;
}
double CNCCodeViewport::GetLinesPerPage()
{
return ((double)m_h + 0.5) / m_pixels_per_line;
}
int CNCCodeViewport::GetNumberOfLines()
{
if (m_nc_code)
return m_nc_code->m_blocks.size();
return 0;
}
0b5efb325This file is pinned to the selected release. Download the original to open it in your design tool.