С наближаването на сесията във ФМИ наближават и сроковете за предаването на проектите :). Този блог пост е посветен на първия проект, който направих за този семестър, който съответно е по VRML. Следва и описанието на проекта:
Risk game – Probabilities calculator
Отдавна не бях сядал да пиша блог пост. Да не кажа че от година и нещо съвсем си зарязах сайта след завършването на академията на Телерик. 🙂 Все пак с този пост се надявам да възобновя този навик, от време на време да сядам и да пиша нещо по-интересно, с което съм се занимавал.
Такъв е и поводът на този пост, който смятам, че би могъл да бъде от полза на любителите на настолната игра “Риск”. Който не е чувал за нея или не я е играл, може да види повече информация за играта “Риск” в Wikipedia. Аз лично също съвсем отскоро я играя, но пък ми стана любопитно и реших да тествам уменията си като напиша един калкулатор на javascript. Какво прави този калкулатор накратко:
- Задава се броя на атакуващите и на защитаващите войници в една битка.
- Калкулаторът изчислява всички шансове, при които битката завършва до край (или защитниците или нападателите остават с 0 войници).
- Като резултат калкулаторът изкарва две таблички. В първата са шансовете на победа на нападателя, като срещу всеки брой оцелели на края на битката пише каква е вероятността за това събитие. Втората таблица е аналогична, но за защитаващата се армия.
- Тези таблици имат 4 колони:
- Брой оцелели след края на битката
- Процент вероятност да има точно толкова оцелели
- Процент вероятност да има толкова или повече оцелели
- Процент вероятност да има по-малко оцелели
Имайки този калкулатор, човек може да си прецени шансовете и да реши дали би влязъл в битка в зависимост от броя войници, с които иска да остане след битката. 🙂 Това е накратко. Ето и едно демо с опростен UI на калкулатора:
Ако искате да го отворите в отделна страница, може да отворите този линк към калкулатора в отделен прозорец.
Надявам се този пост да бъде от полза на някого. Всякакви коментари по темата са добре дошли. 🙂
BigInteger за JavaScript
Малко преди първия изпит по JavaScript в академията на Телерик, реших да си напиша един BigInteger за всеки случай, ако случайно ми се наложи да правя сметки с големи цели числа. Като цяло си припомних детските години, в които ни учеха да събираме, изваждаме и делим числа :), което ако трябва да съм честен се оказа не чак толкова тривиална задачка. 🙂
За имплементацията на моя BigInteger съм използвал представяне на числата под формата на масив от цифри. Не е най-ефективното решение предвид че за всяка цифра от 1 до 9 използвам число в JavaScript, което по принцип е в доста по-големи граници. Бих се пробвал като намеря време да направя нова имплементация, която вътрешно използва не масив от всички цифри, ами такъв от по-големи числа, така че да не се налага да заделям толкова излишна памет за едно многоцифрено число.
Въпреки тези недостатъци трябва да кажа, че все пак имплементацията работи доста бързо и поне аз след известно тестване не съм намерил някакви проблеми с бързодействието.
Ето линк към моя GitHub профил, където може да се свали имплементацията:
Към самия файл най-отгоре съм прикачил и кратка документация, обясняваща основните функционалности на обекта. Ето и един цитат към нея.
///This JS object was written by Deyan Yosifov.///BigInteger keeps an integer number with infinite number of digits.///It supports functions plus, minus and multiply which return another BigInteger object///Also has toString() and toNum functions in it.///for parsing long numbers from string use parseToBigInteger(str)///for parsing from small number use new BigInteger(num)///for parsing from array of digits use new BigInteger(array, hasMinusSign)
Мисля че след известно тестване съм изчистил всички бъгове и обекта работи напълно коректно, но все пак ако някой открие някакви такива бих се радвал да сподели. 🙂
Tetris game – Telerik OOP Teamwork
След тази седмичната екипна работа в Телерик, реших да споделя и резултата от работата на моя отбор – Chipmunk :). Като начало ние си бяхме поставили за цел да направим имплементация на популярната игра Тетрис и като краен резултат, въпреки краткия срок и многото други домашни и задачки, които трябваше да движим успоредно с отборната работа, мисля че като цяло крайния резултат не се получи зле. За краткото време направихме един ООП проект на C#, във вид на конзолно приложение.
Ето и скрийншот от крайния резултат:
Ако някой иска да погледне кода, може да го види качен в моя GitHub профил на този линк: TetrisGame SourceCode.
Там може да бъде видяна и кратка документация с кратки обяснения на основните класове и организацията на кода.
За да свалите играта може да използвате този линк: TetrisGame DownLoad!
Поне към момента не са ми известни бъгове, но ако някой открие, ще се радвам ако ги сподели. 🙂 Enjoy!
Пресмятане на Аритметичен израз с приоритети на операторите
Реших да разкажа, за моята имплементация, на една от задачите от втората част от курса по C# в Telerik Academy, а именно примерна имплементация на алгоритъма Shunting yard, за пресмятане на аритметичен израз.
В случая като въведение в проблема, ще дам един пример, за такъв аритметичен израз и неговото пресмятане със съответните приоритети на операторите:
Важно е тук да се спомене, че най-висок приоритет имат скобите, след тях е операцията повдигане на степен (която е дясно-асоциативна за разлика от другите ляво-асоциативни операции), след това са операциите умножение и деление и накрая с най-нисък приоритет са операциите събиране и изваждане. В израза също така участва и едно-аргументната функция корен квадратен (sqrt), чийто приоритет в случая е излишно да бъде търсен, тъй като за нейния запис използваме скоби, ограждащи аргумента й.
За построяването на дървото на операциите, необходимо ни, за да знаем, коя операция да извършим първа, коя втора и така нататък, докато пресметнем целия израз, съм използвал така наречения Shunting Yard Algorithm. Цъкайки на дадения линк към wikipedia, по надолу в статията ще достигнете и до описанието на последователността на действията в самия алгоритъм, които са:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | -While there are tokens to be read: --Read a token. --If the token is a number, then add it to the output queue. --If the token is a function token, then push it onto the stack. --If the token is a function argument separator (e.g., a comma): ----Until the token at the top of the stack is a left parenthesis, pop operators off the stack onto the output queue. If no left parentheses are encountered, either the separator was misplaced or parentheses were mismatched. --If the token is an operator, o1, then: ----while there is an operator token, o2, at the top of the stack, and either o1 is left-associative and its precedence is less than or equal to that of o2, or o1 has precedence less than that of o2, pop o2 off the stack, onto the output queue; ----push o1 onto the stack. --If the token is a left parenthesis, then push it onto the stack. --If the token is a right parenthesis: ----Until the token at the top of the stack is a left parenthesis, pop operators off the stack onto the output queue. ----Pop the left parenthesis from the stack, but not onto the output queue. ----If the token at the top of the stack is a function token, pop it onto the output queue. ----If the stack runs out without finding a left parenthesis, then there are mismatched parentheses. -When there are no more tokens to read: --While there are still operator tokens in the stack: ----If the operator token on the top of the stack is a parenthesis, then there are mismatched parentheses. ----Pop the operator onto the output queue. -Exit. |
Преди да започна с имплементацията на алгоритъма, аз си създавам една структура PredefinedFunction, която ще ми описва основните параметри на всяка една предефинирана в програмата функция – брой параметри, приемаща функцията; дали е ляво или дясно асоциативна; самите действия, които ще прави функцията върху параметрите си, записани като поле от тип делегат; приоритета на конкретната функция. Ето и имплементацията на тази структура:
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public struct PredefinedFunction { public int parametersCount; public bool isLeftAssociated; public Function.Calculate calculateFunction; public int priority; public PredefinedFunction(int parametersCount, bool isLeftAssociated, Function.Calculate calculateFunction, int priority) : this() { this.parametersCount = parametersCount; this.isLeftAssociated = isLeftAssociated; this.calculateFunction = calculateFunction; this.priority = priority; } } |
След това дефинирам класа Function, който ще съдържа речник от предефинирани функции, като всяка ще знае как да работи, благодарение на свойството Result ползващо полето делегат calculateFunction, на съответната предефинирана функция. Ето и имплементацията на този клас.
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | public class Function { public delegate double Calculate(double[] parameters); private double[] parameters; private string functionName; private static Dictionary<string, PredefinedFunction> functionsLibrary; static Function() { functionsLibrary = new Dictionary<string, PredefinedFunction>(); functionsLibrary.Add("+", new PredefinedFunction(2, true, new Calculate(x => x[0] + x[1]), 1)); functionsLibrary.Add("-", new PredefinedFunction(2, true, new Calculate(x => x[0] - x[1]), 1)); functionsLibrary.Add("*", new PredefinedFunction(2, true, new Calculate(x => x[0] * x[1]), 2)); functionsLibrary.Add("/", new PredefinedFunction(2, true, new Calculate(x => x[0] / x[1]), 2)); functionsLibrary.Add("^", new PredefinedFunction(2, false, new Calculate(x => Math.Pow(x[0], x[1])), 3)); functionsLibrary.Add("pow", new PredefinedFunction(2, true, new Calculate(x => Math.Pow(x[0], x[1])), 4)); functionsLibrary.Add("ln", new PredefinedFunction(1, true, new Calculate(x => Math.Log(x[0])), 4)); functionsLibrary.Add("sqrt", new PredefinedFunction(1, true, new Calculate(x => Math.Sqrt(x[0])), 4)); } public Function(string functionName) { this.functionName = functionName; this.parameters = new double[functionsLibrary[functionName].parametersCount]; } public static Dictionary<string, PredefinedFunction> Library { get { return functionsLibrary; } } public string FunctionName { get { return this.functionName; } } public double this[int index] { get { return this.parameters[index]; } set { this.parameters[index] = value; } } public int ParameterCount { get { return functionsLibrary[functionName].parametersCount; } } public int Precedence { get { return functionsLibrary[functionName].priority; } } public bool IsLeftAssociated { get { return functionsLibrary[functionName].isLeftAssociated; } } public double Result { get { return functionsLibrary[this.functionName].calculateFunction(this.parameters); } } } |
Статичния конструктор Function() има за цел да зареди речника от предефинирани функции, преди използването на класа. Тук е мястото да отбележа, че използването на този речник, свързващ символ в израза с конкретна функция, прави изключително лесно в последствие добавянето на нови и нови функции в кода. Ако искаме да добавим 1 нова функция, трябва да добавим само 1 ред към съществуващия код, описващ как е записа на тази функция в аритметичния израз и какви са нейните предефинирани характеристики (приоритет, асоциативност, конкретни пресмятания с аргументите и броя аргументи).
Тук следва да бъде спомената и имплементацията на Shunting Yard Algorithm:
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | public static void ShuntingYard(string token, Stack<string> output, Stack<string> operators) { #region Number double tempNumber; if (double.TryParse(token, out tempNumber)) { output.Push(token); return; } #endregion Number #region Function or Operator if (Function.Library.ContainsKey(token)) { if (Function.Library[token].priority == 4) { operators.Push(token); } else { while (IsThereHigherOperatorWaiting(token, operators)) { output.Push(operators.Pop()); } operators.Push(token); } return; } #endregion Function or Operator #region Parenthesis if (token == "(") { operators.Push(token); return; } if (token == ")") { GoToPreviousLeftParenthesisAndRemoveThem(output, operators); return; } #endregion Parenthesis #region Comma if (token == ",") { GoToPreviousLeftParenthesis(output, operators); return; } #endregion Comma } |
Имплементацията следва точно горе-описания с думи алгоритъм.
В крайна сметка тази имплементация търси последователността на сметките, които трябва да се извършат, като най-първото пресмятане, ще бъде открито последно. Поради тази причина използвам една глобална променлива от тип стек, която пълня с всеки следващ намерен от алгоритъма “ход”. Изпразвайки стека елемент по елемент в последствие аз ще получа правилната последователност на извършваните операциите, която съответства и на съответното дърво на операциите. Ето и дървото за горния пример за по-голяма яснота.
Пресмятането става посредством метода RNP (идващо от Reversed Polish Notation), който вътрешно използва метода ParseNumberOrFunction().
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 | public static double CalculateRPN() { return ParseNumberOrFunction(); } private static double ParseNumberOrFunction() { double result = 0; if (double.TryParse(RPN.Peek(), out result)) { RPN.Pop(); return result; } Function function = new Function(RPN.Pop()); if (function.IsLeftAssociated) { for (int i = function.ParameterCount - 1; i >= 0; i--) { function[i] = ParseNumberOrFunction(); } } else { for (int i = function.ParameterCount - 1; i >= 0; i--) { function[i] = ParseNumberOrFunction(); } } result = function.Result; return result; } |
Идеята е, че се чете поредния елемент от стека RPN, който е генериран предварително от Shunting Yard алгоритъма и съхранен като глобална променлива Stack<string> RNP. Тук е редно да се спомене, че по принцип не е добре да се използва глобална променлива, което го отчитам като недостатък на кода. Това обаче може лесно да се корегира, като променливата се дефинира да речем в Main метода и после се предава като аргумент на необходимите методи. И все пак в конкретния случай съм използвал глобална променлива като едно неформално решение на проблема. При четене на съответния елемент в стека, ако той е число то то се връща като стойност на метода, а ако е функция или оператор, тогава се извиква рекурсивно същия метод за всеки от аргументите на последната извикана функция. По този начин дървото на пресмятанията се обхожда в дълбочина пресмятайки, най-накрая чрез рекурсията крайния резултат.
Ето и пълния код на моята C# имплементация:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 | using System; using System.Collections.Generic; using System.Threading; using System.Globalization; class CalculateArithmeticalExpression { public struct PredefinedFunction { public int parametersCount; public bool isLeftAssociated; public Function.Calculate calculateFunction; public int priority; public PredefinedFunction(int parametersCount, bool isLeftAssociated, Function.Calculate calculateFunction, int priority) : this() { this.parametersCount = parametersCount; this.isLeftAssociated = isLeftAssociated; this.calculateFunction = calculateFunction; this.priority = priority; } } public class Function { public delegate double Calculate(double[] parameters); private double[] parameters; private string functionName; private static Dictionary<string, PredefinedFunction> functionsLibrary; static Function() { functionsLibrary = new Dictionary<string, PredefinedFunction>(); functionsLibrary.Add("+", new PredefinedFunction(2, true, new Calculate(x => x[0] + x[1]), 1)); functionsLibrary.Add("-", new PredefinedFunction(2, true, new Calculate(x => x[0] - x[1]), 1)); functionsLibrary.Add("*", new PredefinedFunction(2, true, new Calculate(x => x[0] * x[1]), 2)); functionsLibrary.Add("/", new PredefinedFunction(2, true, new Calculate(x => x[0] / x[1]), 2)); functionsLibrary.Add("^", new PredefinedFunction(2, false, new Calculate(x => Math.Pow(x[0], x[1])), 3)); functionsLibrary.Add("pow", new PredefinedFunction(2, true, new Calculate(x => Math.Pow(x[0], x[1])), 4)); functionsLibrary.Add("ln", new PredefinedFunction(1, true, new Calculate(x => Math.Log(x[0])), 4)); functionsLibrary.Add("sqrt", new PredefinedFunction(1, true, new Calculate(x => Math.Sqrt(x[0])), 4)); } public Function(string functionName) { this.functionName = functionName; this.parameters = new double[functionsLibrary[functionName].parametersCount]; } public static Dictionary<string, PredefinedFunction> Library { get { return functionsLibrary; } } public string FunctionName { get { return this.functionName; } } public double this[int index] { get { return this.parameters[index]; } set { this.parameters[index] = value; } } public int ParameterCount { get { return functionsLibrary[functionName].parametersCount; } } public int Precedence { get { return functionsLibrary[functionName].priority; } } public bool IsLeftAssociated { get { return functionsLibrary[functionName].isLeftAssociated; } } public double Result { get { return functionsLibrary[this.functionName].calculateFunction(this.parameters); } } } public static Stack<string> RPN; static void Main() { Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; PrintTestInput("2 + 3 * 5 / ( 4 - 5 ) - 1"); PrintTestInput("2 + 3 * 5 + ( 4 * pow ( ( sqrt ( 4 + ln ( 1 ) ) - 1 ) , 5 ) - 5 ) - 1"); PrintTestInput("2 + 3 * 5 + ( 4 * pow ( ( sqrt ( 4 ) ) , 5 ) - 5 ) - 1"); PrintTestInput("2 + 3 * 5 + ( 4 * ( sqrt ( 4 ) ^ 5 ) - 5 ) - 1"); PrintTestInput("2 + 3 * 5 + ( 4 * sqrt ( 4 ) ^ 5 - 5 ) - 1"); PrintTestInput("2 + 3 * 5 + ( 2 ^ 2 * sqrt ( 4 ) ^ 5 - 5 ) - 1"); PrintTestInput("2 + 3 * 5 + ( 2 ^ 2 * sqrt ( 4 ) ^ 2 ^ 2 * 2 - 5 ) - 1"); PrintTestInput("( 2 + 3 ) * 5"); } public static void PrintTestInput(string input) { Console.WriteLine("At the begining we have the input expression:\n{0}", input); RPN = GenerateRNP(input); Console.WriteLine("First we generate its Polish Notation equivalent:"); PrintRNP(); Console.WriteLine(); RPN = GenerateRNP(input); Console.WriteLine("Finally the calculated result: {0}", CalculateRPN()); Console.WriteLine(); } public static void PrintFunctionLibrary() { foreach (string name in Function.Library.Keys) { Console.WriteLine("function {0}, priority {1}", name, Function.Library[name].priority); } } public static Stack<string> GenerateRNP(string expression) { Stack<string> output = new Stack<string>(); Stack<string> operators = new Stack<string>(); string[] tokens = expression.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < tokens.Length; i++) { ShuntingYard(tokens[i], output, operators); } while (operators.Count > 0) { output.Push(operators.Pop()); } return output; } public static void ShuntingYard(string token, Stack<string> output, Stack<string> operators) { #region Number double tempNumber; if (double.TryParse(token, out tempNumber)) { output.Push(token); return; } #endregion Number #region Function or Operator if (Function.Library.ContainsKey(token)) { if (Function.Library[token].priority == 4) { operators.Push(token); } else { while (IsThereHigherOperatorWaiting(token, operators)) { output.Push(operators.Pop()); } operators.Push(token); } return; } #endregion Function or Operator #region Parenthesis if (token == "(") { operators.Push(token); return; } if (token == ")") { GoToPreviousLeftParenthesisAndRemoveThem(output, operators); return; } #endregion Parenthesis #region Comma if (token == ",") { GoToPreviousLeftParenthesis(output, operators); return; } #endregion Comma } private static void GoToPreviousLeftParenthesis(Stack<string> output, Stack<string> operators) { while (operators.Count > 0) { if (operators.Peek() == "(") { return; } output.Push(operators.Pop()); } } private static void GoToPreviousLeftParenthesisAndRemoveThem(Stack<string> output, Stack<string> operators) { while (operators.Count > 0) { if (operators.Peek() == "(") { operators.Pop(); if (operators.Count > 0 && Function.Library.ContainsKey(operators.Peek())) { if (Function.Library[operators.Peek()].priority == 4) { output.Push(operators.Pop()); } } return; } output.Push(operators.Pop()); } } private static bool IsThereHigherOperatorWaiting(string token, Stack<string> operators) { if (operators.Count == 0) return false; if (Function.Library.ContainsKey(operators.Peek()) && Function.Library[operators.Peek()].priority < 4) { if (Function.Library[token].isLeftAssociated) { if (Function.Library[token].priority <= Function.Library[operators.Peek()].priority) { return true; } } else { if (Function.Library[token].priority < Function.Library[operators.Peek()].priority) { //Console.WriteLine("No left associatives in this homework!"); return true; } } } return false; } public static void PrintRNP() { while (RPN.Count > 0) { Console.Write("{0} ", RPN.Pop()); } } public static double CalculateRPN() { return ParseNumberOrFunction(); } private static double ParseNumberOrFunction() { double result = 0; if (double.TryParse(RPN.Peek(), out result)) { RPN.Pop(); return result; } Function function = new Function(RPN.Pop()); if (function.IsLeftAssociated) { for (int i = function.ParameterCount - 1; i >= 0; i--) { function[i] = ParseNumberOrFunction(); } } else { for (int i = function.ParameterCount - 1; i >= 0; i--) { function[i] = ParseNumberOrFunction(); } } result = function.Result; return result; } } |
Дипломна работа – Футболен стадион
Реших, че е не лоша идея, предвид че от доста познати получих искането да им пратя таблата на дипломната ми работа, да се престраша в крайна сметка и макар да не съм на 100% доволен от постигнатия резултат предимно от липса на време (да си потърся извинение веднага :)) реших да кача таблата в сайта си.
Малко предистория – дипломната работа е на тема “Нов национален футболен стадион” и успях да я защитя с отличие към края на януари 2013 година. Обектът е ситуиран в кв. Обеля, гр. София, в близост до метрото, но това ще може да го види, който иска, по-подробно в таблата към проекта. За моделирането на формата съм използвал писан от мен софтуер – GDL обект за Архикад, на име BezierSurfaceDPY-v14–11-01-2013.gsm, за който може да прочетете повече подробности, следвайки линка към тази статия.
Ето и галерия с таблата:
Как да си намерим и модифицираме тема в WordPress
Тази статия я започнах по питане на една колежка като лично съобщение в системата на Телерик, но се оказа че съобщенията са с максимална дължина от 1500 символа и прецених, че написаното явно е по-подходящо за статия в блога :). Ето и моят опит след първите ми 2-3 дена работа с wordpress, споделен под формата на лично обръщение 🙂
Здрасти! Ами честно казано ми харесва твоята тема, изчистена е. Може би бих потърсил начин да се случи някакъв лек фон на текста на всяка статия, за да не е съвсем цялото с една шарка (може например да е някакъв леко прозрачен фон), но това по-скоро ще го прецениш като сложиш няколко статии и видиш как ще стои. Ако искаш да си намериш друга тема най-лесно е като отидеш в Appearance -> Themes -> Install themes, тъй като там има различни критерии, по които можеш да търсиш. Ако сложиш много критерии много вероятно е да не откриеш нищо. Харесай си 1 или 2, които според теб са най-важни и пусни да търси. Някои теми са платени, така че с тях не се занимавай. Пробвай само безплатните. Принципно аз установих, че която и тема да си свалиш, все ще има един куп неща, които да те дразнят. За щастие това се оправя сравнително лесно. В Appearance -> Editor имаш достъп както до php файловете така и до css файловете. PHP-то поне за мен си е малко hardcore (просто не съм се занимавал никога), но като цяло css-a би трябвало да ти е напълно достатъчен за повечето козметични промени. Хубавото е че темите в общия случай са доста добре направени семантично и всички основни елементи са с някакви добре именувани класове (ако си погледнеш html-а на страницата да речен с помощта на Chrome, ще видиш че почти всеки таг си има някакво id или някакъв или дори няколко класа). Това ти помага много, за да можеш лесно да си селектираш това, което искаш със css и да си му променяш стила. Аз например си скрих коментарите навсякъде освен в блога с помощта на css, сложих си радиусчета на някои рамки, сложих някои фонове и т.н., докато спря да ме дразни толкова цялостния изглед. Ето пример за моите добавки към css-a:
/**************************** DPY start ***********************************************/body.page div#respond{
display:none;
}
a{
text-decoration:none !important;
}
a:hover{
font-weight:bold !important;
}div.entry a{
color:#cc0014;
}a.rar-download{
background:url(‘https://deyan-yosifov.com/pics/rar.png’);
background-repeat:no-repeat;
padding-left:25px;
background-size: 20px;
}a.gdl-download{
background:url(‘https://deyan-yosifov.com/pics/gdl.png’);
background-repeat:no-repeat;
padding-left:16px;
background-size: 12px;
}div#side-bar{
background:#fcfbe1 !important;
border-radius:15px;
}
/*Gallery*/
div#post-76 div.entry p{
padding: 10px !important;
border:1px solid black !important;
border-radius:15px !important;
background:#fcfbe1 !important;
text-align:center !important;
}
div#post-76 div.entry p a{
display:block !important;
}
/*END Gallery*//*Menu item*/
ul#menu-dpy-menu{
margin:2px !important;
}
/*End Menu item*//****************************** DPY end **********************************************/
На много места след селектора и стойността съм използвал !important, като по този начин си гарантирам, че няма някой друг css по-надолу да бие моя. 🙂
Освен това, когато си пишеш някакъв пост или страница в редактора за писане може да си, както в режим Visual, така и в режим Text, като това второто си е html. То ест там можеш да си слагаш разни тагове, да им слагаш класове, които после да използваш за селектиранe от css-a и т.н. Дори можеш да си добавяш script тагове, в които да пишеш javascript, с който да си правиш разни магии 🙂 Това е за сега. Пробвай да си пооцветиш страничката, пък ако си харесаш друга тема пробвай и с нея. При всички положения едва ли ще намериш тема, която да ти хареса на 100% :).
Поздрави!
Ще очаквам коментари и въпроси под темата, ако има такива разбира се. 🙂
Дефиниране на клас Матрица – C# – Част 2 – Задача 6 – Multidimensional Arrays
Условието на задачата е:
6.* Write a class Matrix, to holds a matrix of integers. Overload the operators for adding, subtracting and multiplying of matrices, indexer for accessing the matrix content and ToString().
Тази задача лично на мен ми беше изключително полезна и интересна, тъй като за първи път се сблъсках с дефинирането на клас до детайли като дефиниране на оператор (+, – и т.н.), както и дефиниране на индексатор. За да се имплементира коректно поведението на матриците според установените в математиката дефиниции, е необходимо да се подходи с внимание към проверките при различните оператори, свързани с размерностите на самите матрици, a също така е важна и капсулацията на полетата. Към операциите събиране, изваждане и умножение съм направил проверка за размерностите на матриците и хвърлям изключение ако има проблем с размерите (при събиране и изваждане дименсиите на двете матрици трябва да са равни, а при умножението броя колони на първата матрица трябва да е равен на броя редове на втората). Полетата на класа matrix, rows, columns са private, за да може да са достъпни единствено през публичното свойство Value, което съответно се ползва и от конструкторите и операторите. По този начин си гарантирам че няма да стане някаква грешка и някой да промени информацията за броя на редовете примерно без реално да е променил броя на редовете на самата матрица. В Main метода съм направил няколко теста за различните оператори и случаи.
Ето и примерната имплементация:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | using System; using System.Text; class Matrix { private int[,] matrix; private int rows; private int columns; public int[,] Value { get { return this.matrix; } set { this.matrix = value; this.rows = value.GetLength(0); this.columns = value.GetLength(1); } } public int Rows { get { return this.rows; } } public int Columns { get { return this.columns; } } public Matrix(int[,] matrix) { this.Value = (int[,])matrix.Clone(); } public Matrix(int rows, int cols) : this(new int[rows, cols]) { } public static Matrix operator +(Matrix a, Matrix b) { if (a.Rows != b.Rows || a.Columns != b.Columns) { throw new FormatException("Matrixes must have same dimensions!"); } else { Matrix result = new Matrix(a.Rows, a.Columns); for (int i = 0; i < a.Rows; i++) { for (int j = 0; j < a.Columns; j++) { result[i, j] = a[i, j] + b[i, j]; } } return result; } } public static Matrix operator -(Matrix a, Matrix b) { if (a.Rows != b.Rows || a.Columns != b.Columns) { throw new FormatException("Matrixes must have same dimensions!"); } else { Matrix result = new Matrix(a.Rows, a.Columns); for (int i = 0; i < a.Rows; i++) { for (int j = 0; j < a.Columns; j++) { result[i, j] = a[i, j] - b[i, j]; } } return result; } } public static Matrix operator *(Matrix a, Matrix b) { if (a.Columns != b.Rows) { throw new FormatException("First matrix columns number must be equal to second matrix rows number!"); } else { Matrix result = new Matrix(a.Rows, b.Columns); for (int i = 0; i < a.Rows; i++) { for (int j = 0; j < b.Columns; j++) { for (int k = 0; k < a.Columns; k++) { result[i, j] += a[i, k] * b[k, j]; } } } return result; } } public int this[int i, int j] { get { return this.Value[i, j]; } set { this.Value[i, j] = value; } } public override string ToString() { StringBuilder result = new StringBuilder(); result.Append(this[0, 0]); for (int j = 1; j < this.Columns; j++) { result.AppendFormat(" {0}", this[0, j]); } for (int i = 1; i < this.Rows; i++) { result.AppendFormat("\n{0}", this[i, 0]); for (int j = 1; j < this.Columns; j++) { result.AppendFormat(" {0}", this[i, j]); } } return result.ToString(); } static void Main() { Matrix a = new Matrix(3, 4); a.Value[1, 1] = 5; Matrix b = new Matrix(3, 4); b.Value[1, 1] = 3; Matrix c = new Matrix(4, 2); c.Value[1, 1] = 3; Console.WriteLine("a.Rows: {0}; a.Columns: {1}", a.Rows, a.Columns); Console.WriteLine(a); Console.WriteLine(); Console.WriteLine("b.Rows: {0}; b.Columns: {1}", b.Rows, b.Columns); Console.WriteLine(b); Console.WriteLine(); Console.WriteLine("c.Rows: {0}; c.Columns: {1}", c.Rows, c.Columns); Console.WriteLine(c); Console.WriteLine(); Console.WriteLine("\na+b=\n{0}", a + b); Console.WriteLine("\na-b=\n{0}", a - b); Console.WriteLine("\na*c=\n{0}", a * c); Console.WriteLine("\na+c="); try { Console.WriteLine("\na+c=\n{0}", a + c); } catch (Exception e) { Console.WriteLine(e.Message); } Console.WriteLine("\nb-c="); try { Console.WriteLine("\nb-c=\n{0}", b - c); } catch (Exception e) { Console.WriteLine(e.Message); } Console.WriteLine("\na*b="); try { Console.WriteLine("\na*b=\n{0}", a * b); } catch (Exception e) { Console.WriteLine(e.Message); } int [,] v0 = {{4, 8, 15, 16, 23, 42}}; Matrix v = new Matrix(v0); int[,] t0 = { { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 } }; Matrix t = new Matrix(t0); Console.WriteLine(); Console.WriteLine("v.Rows: {0}; v.Columns: {1}", v.Rows, v.Columns); Console.WriteLine(v); Console.WriteLine(); Console.WriteLine("t.Rows: {0}; t.Columns: {1}", t.Rows, t.Columns); Console.WriteLine(t); Console.WriteLine(); Console.WriteLine("\nv*t=\n{0}", v * t); Console.WriteLine(); int i = 0; int j = 0; Console.WriteLine("(v*t)[{1},{2}]={0}", (v * t)[i, j], i, j); } } |
Имплементация на Shooter Game Navigation като Plug-in за Google Sketch Up
Вероятно, който се е занимавал с моделиране на 3д обекти, под някаква форма се е сблъсквал с програмата Google Sketch Up. Личното ми мнение е, че това е една страхотна програма, изключително лека (зарежда със скоростта на Notepad), а в същото време дава доста големи възможности за свободно моделиране на всевъзможни форми и като цяло поне за мен за създаването на малки по размер 3д обекти и дизайни, както и за концептуални модели, програмата е наистина безценна.
Още повече се запалих по тази програма преди няколко години, когато разбрах, че за Sketch Up си има една цяла общност от ентусиасти (Sketchucation.com), които пишат и публикуват безплатни плъгини (направени за собствени нужди или по поръчка на някой пишещ във форума), които плъгини добавени към програмата вече могат да я направят наистина мощно средство за лесно създаване на модели за дизайн и архитектура например.
Така след като известно време ползвах различни плъгини към програмата, в един момент реших и аз да се пробвам да се науча и да пиша такива. И това се оказа далеч не толкова трудна задача – макар и никога дотогава да не бях и чувал за Ruby, в интернет има изключително удобна и подробна документация на Sketch Up Ruby API и след прочитането на една малка книжка за основите на езика, аз вече можех спокойно да пиша първите си плъгини.
Цялата идея е да се напише един файл с разширение .rb, който да се сложи в инсталационната директория на програмата в папка Plugins. При зареждане на Sketch Up, програмата компилира всички файлове в папка Plugins, и те вече стават активни в текущо отворения прозорец на Sketch Up. Тук е момента и да кажа, че ако си свалите стотина Plugin-a в папката, то вече Sketch Up далеч няма да зарежда вече със скоростта на Notepad, ами по скоро ще се доближи до тази на 3DSMax, което както и да го погледнем не е на добре. 🙂 Затова излишните Plugin-и по-добре ги скрийте в една директория да речем HiddenPlugins, и когато ви потрябват си ги извадете от там.
До към септември 2012 си бях написал десетина плъгина за лични нужди, свързани с някои от архитектурните проекти, върху които работех и честно казано от тогава не ми остава време сядам да се занимавам с това (странно защо този времеви период съвпада със започването ми като студент в академията на Телерик 🙂 ). И все пак въпреки, че имах желание да пипна малко въпросните плъгини, за да бъдат малко по-подходящи за гледане от други хора :), реших да представя поне един от тях, който позволява движението в моделното пространство на Sketch Up да бъде като това в познатите ни компютърни игри като CounterStrike например. Поне според мен този plugin значително улеснява придвижването, особено в по-големи модели, където “зуумванете” и “орбитирането” (стандартните Tool-ове в Sketch Up за навигиране), просто не работят както трябва и навигирането става изключително затруднено.
Ето и линк към моя tool: DPY-Camera-24-09-2012.rar
За да го инсталирате, както казах и по-горе, просто слагате .rar-файла в директорията Plugins на SketchUp и го екстрактвате. В самия архив освен .rb файла има една папка, съдържаща картинки използвани от моят tool, както и Win32API.so файл, който е необходим, тъй като в скрипта съм използвал външни за Ruby функции, за да мога да манипулирам успешно мишката под Windows, тъй като за правилната работа на този tool е необходимо да мога да местя курсора на определена позиция.
Ще започна първо с обяснение за хората, които искат да използват моят tool, но не държат да знаят точно как работи кодът му. След като сте инсталирали плъгина, отваряте Sketch Up, а ако е отворен от преди го рестартирайте (тъй като плъгина се компилира и зарежда като функциониращ tool, при стартиране на програмата и няма да се зареди ако не рестартирате Sketch Up). Намерете иконката в Toolbar-а, както е показано на картиката по-долу.
DPY-Camera се стартира при цъкане на иконката в Toolbar-a. Изключването на tool-a, става посредством натискането на spacebar (което активира tool-a за селекция в Sketch Up), или при натискането на друг бутон, който активира съответен tool (например М за move). Най-долу в message bar-a се изписват помощни съобщения по време на използването на tool-a, указващи клавишите за навигация, стойност на скоростта и др. Скоростта се задава в m/s като ако искате да се движите по-бързо или по-бавно просто пишете цифрено новата стойност и натискате Enter. Това е удобно тъй като в зависимост от размерите на обекта, който искате да огледате, може да се налага да се приближавате детайлно близо(примерно при моделиране дизайна на една мебел), или пък да се придвижвате с много голяма скорост (ако моделирате един градски пейзаж да речем).
След като сте стартирали и направили някакво движение ще забележите, че долу при съобщенията се появява един надпис “Click for MouseCorrection!”. Това е поради един бъг, който не успях да намеря по-умен начин да корегирам, свързан с малко отместване на данните за позицията на мишка от Win32API и от SketchUpRubyAPI. Просто цъкнете веднъж с мишката и ще можете да използвате пълната функционалност на tool-a от тук нататък. Ако не цъкнете – ами отново ще може да се придвижвате безпроблемно, но няма да можете да използвате бонус опцията наречена “Gun Mode” :), при която в последствие ще можете да си включите един мерник и при цъкане да “гръмнете” (или в случая да изтриете от модела) обекта който ви е на мушката (edge, face, group, component или каквото ви се изпречи насреща 🙂 ).
След като веднъж сте кликнали с мишката ще видите, че долу в ляво надписа “MouseCorrection” е изчезнал и на негово място имате опция “GunMode On/Off”. GunMode се включва и изключва с Escape, както е и описано в message bar-a, и при включен GunMode мерникът става кръстче с точка по средата (ако е изключен стои само точката).
Когато цъкнете при включен GunMode до курсора се изписва “BOOM!” и се изтрива обекта, който е на мушката, така че бъдете внимателни при включен мерник 🙂 (не че не можете с undo да си върнете изтритото, но все пак).
В общи линии това е по обяснението за използването на Tool-a. Надявам се да ви харесва и да ви бъде полезен. Имах желанието (но не и времето) за добавяне на повече функционалност – не само можеш да триеш при кликане с мишката ами примерно да рисуваш нещо върху модела или някаква друга функционално каквато може да се сети някой. Ако някой има идеи за подобрение нека пише или нека направо работи върху кода, който ще обясня накратко по-надолу.
Като начало каква е идеята на самия код, за да работи в конкретния случай. Движението със стрелките е просто и ясно – при задържане на някоя стрелките камерата започва да се движи с необходимата скорост в съответната посока. Как работи въртенето на камерата? Ами също не толкова сложно – при преместване на мишката tool-a отчита новата позиция на мишката, запазва мястото на камерата, премества фокусната точка върху някоя точка по лъча получен при свързването на позицията на камерата и новата точка на мишката. След като камерата е преместена нашият tool връща мястото на мишката в първоначалната позиция. Така мишката винаги стои по средата на екрана, във фокуса на камерата и съвпадайки с посоката на движение при задържана стрелка напред.
Поглеждайки в Sketch Up Ruby API можем да видим, че на пръв поглед един tool в скеч ъп има контрол над всичките ни необходими команди за прихващане на събития свързани с мишката и клавиатурата. На втори и трети поглед, почвайки да пишем кода, се сблъскваме с няколко проблема, които са свързани както с непълнотии в API-то, така и с бъгове в някои от съществуващите опции.
На първо време нека кажа какво трябва да се направи, за да си дефинираш един tool в Sketch Up. Ами всъщност е доста просто – просто си правиш един нов клас и него започваш да го пълниш с методите описани в документацията, необходими за да бъде разпозната в последствие от Sketch Up създадената инстанция на този клас като tool, а не като някакъв друг произволен клас. Важен в случая е метода activate, който активира tool-a при създаване на инстанцията на класа. В този метод се случват и началните действия, необходими за работата на нашия tool. Ето я и конкретната имплементация на този метод:
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | def activate camera = Sketchup.active_model.active_view.camera newTarget = Geom::Point3d.linear_combination 1.0, camera.eye, 1.0, camera.zaxis.to_a camera.set camera.eye, newTarget, [0,0,1] @@mouseDouble = false @@boom = false @@eraser = false @@mouseCorrected = false @@vk_up = false @@vk_down = false @@vk_left = false @@vk_right = false @firstPress = true @@animate = true @c = Sketchup.active_model.active_view.center @corX = 0 @corY = 0 @@v = 6/0.0254 @msg = "Use arrow keys and mouse to navigate! Velocity = " + (@@v*0.0254).to_s + " m/s. Change it by typing a desired velocity and pressing Enter." Sketchup::set_status_text(@msg) setCursorPos = Win32API.new("user32", "SetCursorPos", ['I', 'I'], 'V') setCursorPos.Call(@c[0]+@corX,@c[1]+@corY) end |
Тук е и момента да помоля за извинения читателя на тази статия за странното форматиране на кода. Истината е, че в Notepad++ индентирането си изглежда нормално, но пействайки го тук става някакво разминаване, което трябва да оправям ръчно. В момента, в който ми се отвори време, ще се опитам да го оправя, но в случай, че на някой му е неприятно да го гледа от тук, би могъл да отвори файла DPY_CAMERA_24_09_2012.rb, който е в горния архив.
В методa activate си инициализирам началните стойности на глобалните и неглобалните променливи на класа. В самия му край (ред 96 и 97) може да видите и задаването на началното преместване на курсора в центъра на екрана. Тук е и първия проблем със Sketch Up Ruby API, с който трябваше да се сблъсквам – няма метод който да ми позволява преместването на мишката на определена позиция. След извесно търсене в нета, намерих решението на този проблем с външен метод от Win32API, който първо го дефинирам и после го извиквам. Това е един от най-малките проблеми, с които трябваше да се сблъскам, пишейки този плъгин. 🙂
Освен метода activate tool-a може да съдържа още един куп методи, които кръстени по определен начин, описан в Sketch Up Ruby API, биват разпознавани в последствие от Sketch Up и биват използвани за конкретни цели. Такива са методите OnMouseMove, OnKeyDown и други. Няма да ги описвам подробно. Който иска може да си прочете за тях в документацията, в която има и доста подходящи примери.
Накратко ще спомена и другите проблеми, с които се сблъсках. Метода, който използва Sketch Up за отчитане на координатите на текущата позиция на мишката се различава резултатите за координатите от външните методи, които ползвам от Win32API. Това наложи да използвам метод от Win32API и за взимането на координатите, за да няма разминавания. Това се случва в метода onMouseMove:
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | def onMouseMove(flags, x, y, view) getCursorPos = Win32API.new("user32", "GetCursorPos", ['P'], 'V') lpPoint = " " * 8 # store two LONGs getCursorPos.Call(lpPoint) p, q = lpPoint.unpack("LL") # get the actual values ray = view.pickray p-@corX, q-@corY status = ray[1].samedirection? [0,0,1] if(!status) view.camera.set ray[0], ray[1], [0,0,1] end setCursorPos = Win32API.new("user32", "SetCursorPos", ['I', 'I'], 'V') setCursorPos.Call(@c[0]+@corX,@c[1]+@corY) end |
Дотук добре. Истинският проблем обаче се появява, в момента, в който решавам, че искам да мога да включвам един мерник и с цъкане на мишката да “стрелям” и да изтривам обектите пред мерника. В Sketch Up Ruby API има готови класове и методи, с които мога да разбера кой е обекта под мишката ми и да го селектирам и съответно променя или направо изтрия. Това е чудесно, но с наличните разминавания на координатите на позициита на курсора, би било неприятно мерника ми да е нарисуван и да сочи към един обект, а аз като “стрелям” да изтрия някой съседен на него такъв. След като търсих начин да успея да унифицирам координатите от Sketch Up Ruby API и Win32API, за съжаление най-умното, което успях измисля е да поискам от потребителя на моя tool да направи просто един клик с мишката и отчитайки разликата от координатите на двете API-та, аз оттук нататък да използвам две променливи за корекция по x и корекция по y, като по този начин съм сигурен че мерника ми ще е напълно точен :). Ако някой успее да измисли по-добро решение на този проблем, ще се радвам да го сподели.
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | def onLButtonDown(flags, x, y, view) if(!@@mouseCorrected) @@mouseCorrected = true @corX = @c[0] - x @corY = @c[1] - y end if(@@eraser and @@mouseCorrected) @@boom = true ph = view.pick_helper ph.do_pick x,y best = ph.best_picked if(!best.deleted?) then best.erase! end end =begin ### Dictionaries and Attributes ### if(!@@eraser and @@mouseCorrected) ph = view.pick_helper ph.do_pick x,y best = ph.best_picked inputpoint = view.inputpoint x, y if(!best.deleted?) best.set_attribute best.typename, "test", inputpoint.position best.attribute_dictionaries.each { |dict| print="" dict.each { |key,value| print += dict.name.to_s + " --> " + key.to_s + " = " + value.to_s + "\n" } UI.messagebox(print) } end end =end end #end of LButtonDown |
Големият закоментиран код в метода onLButtonDown (от ред 152 до ред 169), не е оставен там случайно. Тук съм си правил ескперименти с различни други функционалности, които биха могли да се добавят при кликане с мишката, освен сегашната функционалност за стреляне. Ако някои има идеи за реализация на рисуване с мишката или каквото и да било друго, може би тук е мястото, където трябва да добави малко код и да добави необходимата според него допълнителна функционалност на tool-а.
Последният проблем, с който се сблъсках, беше свързан с един бъг в Sketch Up Ruby API, свързан със събитието задържан клавиш. В документацията е записано, че при onKeyDown аз мога да имам информация за това дали клавиша е натиснат еднократно или е задържан. Това е вярно, но за съжаление работи само под Mac. Под Windows има някакъв бъг и тази опция просто не работи. Затова, вече решил проблема с ротацията на камерата, аз се сблъсках неочаквано и с проблем с нейното транслиране, тъй като искам при задържан бутон (стрелка) камерата да се транслира плавно с определена скорост в дадена посока. За да реализирам това успешно използвам комбинация от методите onKeyDown и onKeyUp, които ми дават информация кога някоя стрелка е натисната и кога е пусната. Тази информация съответно я пазя в една булева променлива за всяка от стрелките като тези променливи ги правя глобални за класа на моя tool. Всичко, което остава да направя, е да направя анимация на транслирането на камерата в зависимост от състоянието на тези булеви променливи. Анимация в Sketch Up се прави като се дефинира един допълнителен клас за анимацията. Този клас трябва да има един метод на име nextFrame, чието име мисля че достатъчно ясно говори за какво служи :). Ето и конкретната имплементация на класа за анимацията:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | class Animation def initialize @@DPY = DPYmoveCAMERAtool.new @camera = Sketchup.active_model.active_view.camera @time = Sketchup.active_model.active_view.average_refresh_time * 10 @v = @@DPY.velocity @msg = "" @newEye = Geom::Point3d.new @newTarget = Geom::Point3d.new end def nextFrame(view) @camera = view.camera @time = view.average_refresh_time * 10 @v = @@DPY.velocity if (@@DPY.vk_up) @newEye = Geom::Point3d.linear_combination 1.0, @camera.eye, @v*@time, @camera.zaxis.to_a @newTarget = Geom::Point3d.linear_combination 1.0, @camera.target, @v*@time, @camera.zaxis.to_a view.camera.set @newEye, @newTarget, [0,0,1] end if (@@DPY.vk_down) @newEye = Geom::Point3d.linear_combination 1.0, @camera.eye, -@v*@time, @camera.zaxis.to_a @newTarget = Geom::Point3d.linear_combination 1.0, @camera.target, -@v*@time, @camera.zaxis.to_a view.camera.set @newEye, @newTarget, [0,0,1] end if (@@DPY.vk_left) @newEye = Geom::Point3d.linear_combination 1.0, @camera.eye, -@v*@time, @camera.xaxis.to_a @newTarget = Geom::Point3d.linear_combination 1.0, @camera.target, -@v*@time, @camera.xaxis.to_a view.camera.set @newEye, @newTarget, [0,0,1] end if (@@DPY.vk_right) @newEye = Geom::Point3d.linear_combination 1.0, @camera.eye, @v*@time, @camera.xaxis.to_a @newTarget = Geom::Point3d.linear_combination 1.0, @camera.target, @v*@time, @camera.xaxis.to_a view.camera.set @newEye, @newTarget, [0,0,1] end @msg = "Velocity = " + (@v*0.0254).to_s + " m/s. Position in space --> " + view.camera.eye[0].to_s + " " + view.camera.eye[1].to_s + " " + view.camera.eye[2].to_s if(!@@DPY.mouseCorrected) then @msg = @msg + ". Click for MouseCorrection!" end if(@@DPY.mouseCorrected) @msg = @msg + ". Esc for GunMode On/Off: " if(@@DPY.eraser) then @msg = @msg + "On" end if(!@@DPY.eraser) then @msg = @msg + "Off" end end if(!@@DPY.animate) @msg = "" end Sketchup::set_status_text(@msg) view.show_frame return @@DPY.animate end end |
Самата анимация се стартира при създаване на инстанция на класа Animation, което аз правя при първото извикване на метода onKeyDown от класа на моя tool:
197 198 199 200 201 202 203 204 205 206 207 208 209 | def onKeyDown(key, repeat, flags, view) if(@firstPress) @firstPress = false Sketchup.active_model.active_view.animation = Animation.new end if (key == VK_UP) then @@vk_up = true end if (key == VK_DOWN) then @@vk_down = true end if (key == VK_RIGHT) then @@vk_right = true end if (key == VK_LEFT) then @@vk_left = true end end |
Кореспонденцията между двата класа я правя посредством глобалните променливи на класа на моя tool (това са тези променливи, започващи с двете кльомби :)).
И така имайки вече цялата функционално – tool + animation, можем да заредим нашия tool в toolbar-a и при кликане да го стартираме:
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 | ######Creating new toolbar item toolbar = UI::Toolbar.new "DPY_CAMERA" ######Adding new command to the toolbar item DPY_CAMERA = UI::Command.new("DPY_CAMERA") { Sketchup.active_model.start_operation 'DPY_CAMERA', true Sketchup.active_model.select_tool DPYmoveCAMERAtool.new Sketchup.active_model.commit_operation } DPY_CAMERA.small_icon = File.join("DPY_CAMERA", "DPY_CAMERA_small.jpg") DPY_CAMERA.large_icon = File.join("DPY_CAMERA", "DPY_CAMERA_large.jpg") toolbar = toolbar.add_item DPY_CAMERA toolbar.show |
И това е всичко! Събирайки целия код в едно получаваме финалния резултат:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | require 'Win32API' class Animation def initialize @@DPY = DPYmoveCAMERAtool.new @camera = Sketchup.active_model.active_view.camera @time = Sketchup.active_model.active_view.average_refresh_time * 10 @v = @@DPY.velocity @msg = "" @newEye = Geom::Point3d.new @newTarget = Geom::Point3d.new end def nextFrame(view) @camera = view.camera @time = view.average_refresh_time * 10 @v = @@DPY.velocity if (@@DPY.vk_up) @newEye = Geom::Point3d.linear_combination 1.0, @camera.eye, @v*@time, @camera.zaxis.to_a @newTarget = Geom::Point3d.linear_combination 1.0, @camera.target, @v*@time, @camera.zaxis.to_a view.camera.set @newEye, @newTarget, [0,0,1] end if (@@DPY.vk_down) @newEye = Geom::Point3d.linear_combination 1.0, @camera.eye, -@v*@time, @camera.zaxis.to_a @newTarget = Geom::Point3d.linear_combination 1.0, @camera.target, -@v*@time, @camera.zaxis.to_a view.camera.set @newEye, @newTarget, [0,0,1] end if (@@DPY.vk_left) @newEye = Geom::Point3d.linear_combination 1.0, @camera.eye, -@v*@time, @camera.xaxis.to_a @newTarget = Geom::Point3d.linear_combination 1.0, @camera.target, -@v*@time, @camera.xaxis.to_a view.camera.set @newEye, @newTarget, [0,0,1] end if (@@DPY.vk_right) @newEye = Geom::Point3d.linear_combination 1.0, @camera.eye, @v*@time, @camera.xaxis.to_a @newTarget = Geom::Point3d.linear_combination 1.0, @camera.target, @v*@time, @camera.xaxis.to_a view.camera.set @newEye, @newTarget, [0,0,1] end @msg = "Velocity = " + (@v*0.0254).to_s + " m/s. Position in space --> " + view.camera.eye[0].to_s + " " + view.camera.eye[1].to_s + " " + view.camera.eye[2].to_s if(!@@DPY.mouseCorrected) then @msg = @msg + ". Click for MouseCorrection!" end if(@@DPY.mouseCorrected) @msg = @msg + ". Esc for GunMode On/Off: " if(@@DPY.eraser) then @msg = @msg + "On" end if(!@@DPY.eraser) then @msg = @msg + "Off" end end if(!@@DPY.animate) @msg = "" end Sketchup::set_status_text(@msg) view.show_frame return @@DPY.animate end end class DPYmoveCAMERAtool def initialize cursorPath2 = Sketchup.find_support_file ("DpyCameraCursor2.png", "Plugins/DPY_CAMERA/") @@cursor2 = UI.create_cursor(cursorPath2, 5, 5) #cursorPath1 = Sketchup.find_support_file ("DpyCameraCursor1.png", "Plugins/DPY_CAMERA/") #@@cursor1 = UI.create_cursor(cursorPath1, 5, 5) end def onSetCursor UI.set_cursor(@@cursor2) end def activate camera = Sketchup.active_model.active_view.camera newTarget = Geom::Point3d.linear_combination 1.0, camera.eye, 1.0, camera.zaxis.to_a camera.set camera.eye, newTarget, [0,0,1] @@mouseDouble = false @@boom = false @@eraser = false @@mouseCorrected = false @@vk_up = false @@vk_down = false @@vk_left = false @@vk_right = false @firstPress = true @@animate = true @c = Sketchup.active_model.active_view.center @corX = 0 @corY = 0 @@v = 6/0.0254 @msg = "Use arrow keys and mouse to navigate! Velocity = " + (@@v*0.0254).to_s + " m/s. Change it by typing a desired velocity and pressing Enter." Sketchup::set_status_text(@msg) setCursorPos = Win32API.new("user32", "SetCursorPos", ['I', 'I'], 'V') setCursorPos.Call(@c[0]+@corX,@c[1]+@corY) end def draw(view) if(@@eraser and @@mouseCorrected) view.drawing_color = "red" d = 5 l = 25 view.draw2d GL_LINES, [@c[0],@c[1] - d,0],[@c[0],@c[1] - l,0] , [@c[0],@c[1] + d,0],[@c[0],@c[1] + l,0] , [@c[0] - d,@c[1],0],[@c[0] - l,@c[1],0] , [@c[0] + d,@c[1],0],[@c[0] + l,@c[1],0] if(@@boom) then status = view.draw_text [@c[0] + d,@c[1] + d,0], "BOOM!" end if(@@mouseDouble = true) @@boom = false @@mouseDouble = false end end end def deactivate(view) @@animate = false end def onCancel(reason, view) @@eraser = !@@eraser end def onUserText(text, view) if(text.to_f) then @@v = text.to_f/0.0254 end end def onLButtonDoubleClick(flags, x, y, view) if(@@eraser and @@mouseCorrected) @@mouseDouble = true @@boom = true ph = view.pick_helper ph.do_pick x,y best = ph.best_picked if(!best.deleted?) then best.erase! end end end def onLButtonDown(flags, x, y, view) if(!@@mouseCorrected) @@mouseCorrected = true @corX = @c[0] - x @corY = @c[1] - y end if(@@eraser and @@mouseCorrected) @@boom = true ph = view.pick_helper ph.do_pick x,y best = ph.best_picked if(!best.deleted?) then best.erase! end end =begin ### Dictionaries and Attributes ### if(!@@eraser and @@mouseCorrected) ph = view.pick_helper ph.do_pick x,y best = ph.best_picked inputpoint = view.inputpoint x, y if(!best.deleted?) best.set_attribute best.typename, "test", inputpoint.position best.attribute_dictionaries.each { |dict| print="" dict.each { |key,value| print += dict.name.to_s + " --> " + key.to_s + " = " + value.to_s + "\n" } UI.messagebox(print) } end end =end end #end of LButtonDown def onLButtonUp(flags, x, y, view) @@boom = false end def onMouseMove(flags, x, y, view) getCursorPos = Win32API.new("user32", "GetCursorPos", ['P'], 'V') lpPoint = " " * 8 # store two LONGs getCursorPos.Call(lpPoint) p, q = lpPoint.unpack("LL") # get the actual values ray = view.pickray p-@corX, q-@corY status = ray[1].samedirection? [0,0,1] if(!status) view.camera.set ray[0], ray[1], [0,0,1] end setCursorPos = Win32API.new("user32", "SetCursorPos", ['I', 'I'], 'V') setCursorPos.Call(@c[0]+@corX,@c[1]+@corY) end def onKeyDown(key, repeat, flags, view) if(@firstPress) @firstPress = false Sketchup.active_model.active_view.animation = Animation.new end if (key == VK_UP) then @@vk_up = true end if (key == VK_DOWN) then @@vk_down = true end if (key == VK_RIGHT) then @@vk_right = true end if (key == VK_LEFT) then @@vk_left = true end end def onKeyUp(key, repeat, flags, view) if (key == VK_UP) then @@vk_up = false end if (key == VK_DOWN) then @@vk_down = false end if (key == VK_LEFT) then @@vk_left = false end if (key == VK_RIGHT) then @@vk_right = false end end def animate @@animate end def vk_up @@vk_up end def vk_down @@vk_down end def vk_left @@vk_left end def vk_right @@vk_right end def velocity @@v end def mouseCorrected @@mouseCorrected end def eraser @@eraser end end # end class DPYmoveCAMERAtool definition ######Dobawqne na toolbar toolbar = UI::Toolbar.new "DPY_CAMERA" ######Dobawqne na komanda tetrahedron kam toolbara DPY_CAMERA = UI::Command.new("DPY_CAMERA") { Sketchup.active_model.start_operation 'DPY_CAMERA', true Sketchup.active_model.select_tool DPYmoveCAMERAtool.new Sketchup.active_model.commit_operation } DPY_CAMERA.small_icon = File.join("DPY_CAMERA", "DPY_CAMERA_small.jpg") DPY_CAMERA.large_icon = File.join("DPY_CAMERA", "DPY_CAMERA_large.jpg") toolbar = toolbar.add_item DPY_CAMERA toolbar.show |
Имплементация на повърхнина на Безие на GDL script за Архикад
Вероятно всеки, който се е занимавал професионално с проектиране (най-вече в областта на строителството, дизайна и други), има своите предпочитания към софтуерния продукт, който ползва ежедневно и който му спестява един куп усилия, за по-лесно и удобно решаване на проектантски проблеми и задачи. За съжаление всеки продукт обикновено заедно със своите предимства, носи и своите ограничения и недостатъци, независимо дали говорим за Архикад, Ревид, 3DsMax, АutoCad и др. Може би единствено AutoCAD не беше подходящ за горния списък, тъй като личното ми мнение е, че той носи със себе си единствено и само недостатъци, без нито едно предимство, пред който и да е друг софтуер за 3D моделиране и изготвяне на чертежи :). В съвременния свят, предоставящ възможност за работа едновременно в 3D и 2D без късане на връзката между модела, чертежа и количествата, смятам за крайно неадекватно, че в практиката все още масово се работи с някакви двумерни драсканици, наречени чертежи, които не дават никаква пространствена представа за цялостното решение, включващо всички специалности (архитектура, ОВК, ВиК, конструкция и т.н.) и по този начин се дава предпоставка за допускане на множество конфликтни места в проекта, които се установяват обикновено на късен етап, по време на строителството. Дотук с AutoCAD и производните нему проблеми. 🙂 Сега ще започна с истинската идея на тази статия, а именно една добавка под формата на GDL обект за Архикад.
Ето линк към GDL обекта: BezierSurfaceDPY-v14–11-01-2013.gsm
Принципно обекта е писан за ArchiCAD 14. Разбира се съвсем спокойно ще тръгне на по-новите версии, както между впрочем и на по-ниските (за по ниските ще трябва първо да се зареди като библиотечен елемент в Архикад 14 и след това документа да се запише на по-ниска версия).
Каква е идеята на обекта и каква беше причината да хвърля усилието и да го напиша? По принцип, започвайки да работя на Архикад преди години, в началото мислех, че това е една ужасна програма, поставяща огромни да ги наречем творчески ограничения върху проектантската дейност. В последствие обаче, посвиквайки с основните идеи на програмата, трябва да кажа, че наистина отчитам голямото усилие, което е хвърлено, за да може програмата наистина да бъде изключително в помощ на проектанта за бързо и качествено изготвяне на целия проект от концептуалното моделиране и измисляне, до създаване на детайлен 3D модел и съпътстващите го задължителни двумерни чертежи. Най-хубавото е, че веднъж хвърлено усилието за правилно изграждане на работния модел, в последствие всички промени, които се налагат (като каприз на инвеститора или дори собствен каприз на проектанта) не представляват особен проблем и времето за нанасянето на корекциите и отразяването им на всички чертежи става наистина малко поради неразривната връзка между модела и чертежа.
И все пак наред с предимствата, за съжаление до голяма степен проблема с творческото ограничение, свързано с ограниченията в тримерното моделиране, които налага програмата, все още не е добре решен (макар и в новите версии на Архикад да се правят такива опити, все още са доста назад от това, което бих желал да се случи). Използването на външни програми за моделиране като 3DsMax например изобщо не е добър вариант, тъй като от една страна се къса връзка с модела, от друга страна 3DsMax работи равнини елементи (лица, ръбове и върхове), което при големи модели отнема ужасно много памет, с която съвременните компютри просто не разполагат (в офиса, в който работя съм използвал наистина мощни съвременни машини и дори те срещат сериозни затруднения със справянето с такива модели). Дори и да можеха да се справят с големите модели, самият факт че обемите не са плътни (имат само лица, ръбове и върхове) усложнява работата върху чертежите, тъй като при разрез през такъв обект, вместо да се получи разрез през плътно тяло се получава разрез през куха кутия, което в последствие трябва да се оправя ръчно и е доста досадно.
Ето защо, решавайки всички тези проблеми, аз се обърнах към вградения в Архикад GDL script, който позволява сравнително лесно моделиране на параметрични геометрични (макар и не задължително) обекти. Хубавото е, че тези обекти дават възможност да бъдат плътни, което при големи обекти работи много по-добре откъм памет в сравнение с кухите многостени, а освен това се поддържат всички настройки за разрез и 2D графика, които се поддържат в Архикад.
В случая за целта аз съм използвал небезизвестната повърнина, на името на открилия я френския математик и инженер Пиер Безие. Повърхнината се дефинира посредством интерполирането между няколко наброй наредени точки в пространството. Нямам за цел в статията да обяснявам математическата дефиниция на самата повърхнина, макар и да я смятам за доста интересна. На читателя, който се интересува, бих препоръчал две статии от wikipedia: Bezier surface и Bezier curve, като особено втората статия считам за изключително полезна, тъй като кривите на Безие са в основата на цялата идея и разбирайки ги тях почти няма да има проблем с разбирането на повърхнините на Безие (повърнините се получават в следствие на интерполиране между няколко наредени пространствени криви на Безие). Във втората статия има много полезни анимирани картинки, които наистина много нагледно показват идеята за формообразуването в зависимост от степента на кривата (от броя на контролните точки).
След като заредите обекта като библиотечен елемент в Архикад (през Library manager), ще можете да го намерите при другите GDL обекти в Embedded Library и маркирайки елемента ще видите подобен на долния екран с настройките на съответния обект.
Тук е мястото да кажа, че самият обект е разработван за лични цели(за да мога успешно да моделирам дипломния си проект, към който ще дам снимки по-долу), поради което не всички функционалности са напълно завършени (оказа се че съчетаването едновременно на работа в архитектурно ателие, ученето за Telerik Academy и правенето на дипломна работа в университета не е много лесно да се реализира :)). Ще се опитам спомена по-надолу накратко какво съм успял да постигна и за какво не ми е стигнало времето, но съм имал доброто желание 🙂 и може би в някакъв бъдещ момент в следваща версия на GDL обекта, ще направя необходимите добавки.
По менюто в частта Parameters – тук се съдържат абсолютно всички параметри за контролирането на обекта. Ще карам точка по точка:
- Точки на Безие – Това са координатите на 16те точки контролиращи кривата. На практика тези параметри не се използват от потребителя (координатите автоматично си се генерират и прегенерират при местенето на hotspot-овете и при прилагането на различни трансформации към обекта (като хомотетия например). Важна обаче е опцията режим на кривата – при линейния режим се работи само с 4 контролни точки, което е в пъти по лесно за контролиране, отколкото кубичния режим с 16 точки. С линейния режим обаче може да се постига единствено формата Хиперболичен-параболоид (което е би-линейна повърхнина на Безие и съответно често е наричана и праволинейна повърхнина). Кубичният режим позволява много по-голям контрол над кривината и тангентите на повърхнината, поради което могат да се постигат много по-разнообразни и интересни форми. Моят принцип на работа е следния – в началото си пускам линейния режим докато си наместя годе долу контурните точки, както си ги представям, че трябва да станат. След това включвам кубичния режим и си играя с другите точки така че да измоделирам желаната финална форма.
- Гъстота на мрежата – тук имате възможност да контролирате гъстотата на мрежата в двете направления на повърхнината, което е важно както за гладкостта на кривата, така и за растера (ако например моделирате остъклена фасада и търсите постигане на някакъв растер с определени ограничения в размерите на полетата). Мрежата се начленява на равни интервали за u и v (двата параметъра дефиниращи формообразуването на тази параметрична крива).
- 3D детайли – Тук се намират основните параметри, които ще са предполагам доста често използвани. Това се параметрите за оразмеряване на сеченията на елементите, както и за включването/изключването на всеки вид от тях (линейни елементи – могат да бъдат както цилиндрични, така и тънки линии, така и да липсват; повърхнинни елементи – могат да имат дебелина, могат и да са плоски, могат и да липсват; контролни елементи – линии и точки използвани за визуализирането на контролните точки дефиниращи кривата).
- Hotspots – Това е доста важна секция, даваща поне за мен най-добрата функционалност на обекта с възможността той да бъде моделиран посредством “горещи точки” :). За по удобна работа в 3D в зависимост от целите, може да се включва/изключва възможността за придвижване по всяка една от 3-те посоки x, y или z, както в 3D така и в план.
- Материали – ами тук се настройват материалите на различните елементи(линейни, повърхнини и др.)
- 2D графика – това са рапидографите и щрихите използвани при изготвянето на двумерните чертежи
- Скрий – това е една функционалност, която добавих в последния момент за направата на дипломната ми, поради необходимостта да скрия диагоналните елементи в мрежата. Тази функционалност не е довършена в частите си различни от скриване на диагонали.
По менюто в UI частта кръстена “Повърхнина на Безие – Деян Йосифов – 11.01.2013 година” – тази секция съдържа елементи от частта Parameters, които съм сметнал за най-важни и за които съм искал да има бърз и лесен достъп чрез UI частта. Единствената част, която е нова в сравнение с частта Parameters, е секцията “Трансформации”, където може да се извърши грубо оразмеряване на повърхнината с хомотетии по x, y или z. Това е полезно, когато например начално имате повърнина с размери 2 на 2 метра, а вие искате да направите нещо доста по-голямо с размерите да речем 20 на 20. Вместо да местите всяка от контролните точки, може просто да напишете необходимия коефициент за хомотетия в необходимата посока (за горния пример коефициента е 10 тъй като искаме 10 пъти да се увеличи размера). След като сте въвели коефициента, за да стане активно оразмеряването трябва да цъкнете на бутона до съответната форма за коефициента (например бутона “Хомотетия по X”).
Това е накратко за постигнатите към този момент опции и функционалности. Ето и няколко картинки от моята дипломна работа (на тема футболен стадион), за моделирането на която съм използвал показания горе GDL обект:
На края на статията ще се опитам с няколко думи да обясня идеята на имплементацията. Като начало каква е идеята на писането на GDL script и къде човек може да се научи как точно се прави това? Ами отговорът е доста лесен – в Help менюто на Архикад може да намерите pdf файл на име GDL Reference Guide, където е описана цялата идея в сравнително кратка документация с линкове между страниците. Ето и screenshot къде точно се намира документацията:
Като цяло GDL е изключително прост и окастрен откъм опции скриптов език. Под окастрен имам предид, че няма възможност да пишете собствени методи и функции, няма while цикъл и други такива иначе толкова полезни неща, имащи ги в нормалните езици за програмиране :). От към цикли езикът разполага с For цикъл. Също така има If-Else функционалност, което е добре :). Има GoTo, което по принцип е ужасно, но в краен случай може и до него да опрете, предвид липсата на while цикъл. Има опции да вкарвате някакви данни във буфера с командата Put и в последствие с командата Get или командата Use да използвате определен брой стойности от буфера, който работи на принципа на опашката (пръв влязъл пръв излиза). Идеята на този буфер е ако генерирате с някакъв цикъл да речем някакви параметри (да кажем координати) след това да може да ги използвате в някоя от вградените в GDL функции. Разгледайте документацията. Любопитно е :).
Как е организиран конкретния GDL обект. За да го отворите трябва да отидете във File -> Libraries and Objects -> Open object и ако сте маркирали преди това обекта то така ще отворите неговия script.
В секцията Parameters се дефинират параметрите на обекта като всеки параметър си има име, тип(дължина, ъгъл, рапитограф, щрих, булев, текст и др.) и начална стойност. Имената на параметрите ще се използват в последствие в скрипта.
Следващата важна секция е Master script. Там се съдържа както 2D, така и 3D, така и всякакъв друг скрипт, който е важно да се знае, че се изпълнява преди всеки друг скрипт. Тук аз си инициализирам началните масиви, необходими за запаметяване на координатите на точките от мрежата, както и си пресмятам всички точки от мрежата в зависимост от режима на кривата (линеен или кубичен). Ето го и скрипта:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | UNid = 1 !!!---Transform Variables Declaration---!!! DIM ii[3] DIM jj[3] DIM kk[3] length = 0 !!!---END Transform Variables Declaration---!!! !!!---HyperbolicParapoloidMode---!!! IF mode = "ëèíååí" THEN PARAMETERS bx[1][2] = (2/3)*bx[1][1] + (1/3)*bx[1][4] PARAMETERS bx[1][3] = (1/3)*bx[1][1] + (2/3)*bx[1][4] PARAMETERS by[1][2] = (2/3)*by[1][1] + (1/3)*by[1][4] PARAMETERS by[1][3] = (1/3)*by[1][1] + (2/3)*by[1][4] PARAMETERS bz[1][2] = (2/3)*bz[1][1] + (1/3)*bz[1][4] PARAMETERS bz[1][3] = (1/3)*bz[1][1] + (2/3)*bz[1][4] PARAMETERS bx[4][2] = (2/3)*bx[4][1] + (1/3)*bx[4][4] PARAMETERS bx[4][3] = (1/3)*bx[4][1] + (2/3)*bx[4][4] PARAMETERS by[4][2] = (2/3)*by[4][1] + (1/3)*by[4][4] PARAMETERS by[4][3] = (1/3)*by[4][1] + (2/3)*by[4][4] PARAMETERS bz[4][2] = (2/3)*bz[4][1] + (1/3)*bz[4][4] PARAMETERS bz[4][3] = (1/3)*bz[4][1] + (2/3)*bz[4][4] PARAMETERS bx[2][1] = (2/3)*bx[1][1] + (1/3)*bx[4][1] PARAMETERS bx[3][1] = (1/3)*bx[1][1] + (2/3)*bx[4][1] PARAMETERS by[2][1] = (2/3)*by[1][1] + (1/3)*by[4][1] PARAMETERS by[3][1] = (1/3)*by[1][1] + (2/3)*by[4][1] PARAMETERS bz[2][1] = (2/3)*bz[1][1] + (1/3)*bz[4][1] PARAMETERS bz[3][1] = (1/3)*bz[1][1] + (2/3)*bz[4][1] PARAMETERS bx[2][4] = (2/3)*bx[1][4] + (1/3)*bx[4][4] PARAMETERS bx[3][4] = (1/3)*bx[1][4] + (2/3)*bx[4][4] PARAMETERS by[2][4] = (2/3)*by[1][4] + (1/3)*by[4][4] PARAMETERS by[3][4] = (1/3)*by[1][4] + (2/3)*by[4][4] PARAMETERS bz[2][4] = (2/3)*bz[1][4] + (1/3)*bz[4][4] PARAMETERS bz[3][4] = (1/3)*bz[1][4] + (2/3)*bz[4][4] PARAMETERS bx[2][2] = (2/3)*bx[1][2] + (1/3)*bx[4][2] PARAMETERS bx[3][2] = (1/3)*bx[1][2] + (2/3)*bx[4][2] PARAMETERS by[2][2] = (2/3)*by[1][2] + (1/3)*by[4][2] PARAMETERS by[3][2] = (1/3)*by[1][2] + (2/3)*by[4][2] PARAMETERS bz[2][2] = (2/3)*bz[1][2] + (1/3)*bz[4][2] PARAMETERS bz[3][2] = (1/3)*bz[1][2] + (2/3)*bz[4][2] PARAMETERS bx[2][3] = (2/3)*bx[1][3] + (1/3)*bx[4][3] PARAMETERS bx[3][3] = (1/3)*bx[1][3] + (2/3)*bx[4][3] PARAMETERS by[2][3] = (2/3)*by[1][3] + (1/3)*by[4][3] PARAMETERS by[3][3] = (1/3)*by[1][3] + (2/3)*by[4][3] PARAMETERS bz[2][3] = (2/3)*bz[1][3] + (1/3)*bz[4][3] PARAMETERS bz[3][3] = (1/3)*bz[1][3] + (2/3)*bz[4][3] ENDIF !!!---END HyperbolicParapoloidMode---!!! !!!---Points Preparations---!!! IF MAX(u, v)>-1 AND MAX(u, v)9 AND MAX(u, v)19 AND MAX(u, v)29 AND MAX(u, v)39 AND MAX(u, v)49 AND MAX(u, v)59 AND MAX(u, v)69 AND MAX(u, v)79 AND MAX(u, v)89 AND MAX(u, v) |
Тук е мястото да спомена, че коментарите в GDL започват със знака “!”. Такива съм използвал доста, дори само за лично удобство, за да се ориентирам по-лесно в кода (както казах тук няма методи и други такива улеснения, така че писането е доста).
Следва 2D скрипта. Там се дефинира двумерната графика в плана, която в случая съм задал да бъде просто Монжова проекция на обекта отгоре. Тук също така се дефинират и хотспотовете, които са за плана.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | pen linii fill fill_type project2{2} 3, 270, 32+3, back_pen, 0, 0, 90 !!!---Hotspots2D---!!! IF Hotspots2D THEN FOR i=1 TO 4 FOR j=1 TO 4 IF mode = "êóáè÷åí" OR (mode = "ëèíååí" AND (i=1 OR i=4) AND (j=1 OR j=4)) THEN hotspot2 bx[i][j], by[i][j], UNid, bx[i][j], 2 : UNid=UNid+1 hotspot2 0, by[i][j], UNid, bx[i][j], 1 + 128 : UNid=UNid+1 hotspot2 -1, by[i][j], UNid, bx[i][j], 3 : UNid=UNid+1 hotspot2 bx[i][j], by[i][j], UNid, by[i][j], 2 : UNid=UNid+1 hotspot2 bx[i][j], 0, UNid, by[i][j], 1 + 128 : UNid=UNid+1 hotspot2 bx[i][j], -1, UNid, by[i][j], 3 : UNid=UNid+1 ENDIF NEXT j NEXT i ENDIF !!!---END Hotspots2D---!!! |
Следва 3D скрипта, който разбира се е основната част от имплементацията. Там в зависимост от стойностите на параметрите генерирам необходимите 3-мерни обекти, както и hotspots, отново ако са включени от потребителя като параметри. В тази част от кода има множество линейни трансформации, повечето от който ги правя чрез матрица на трансформациите, тъй като повечето обекти в GDL скрипт се генерират от готови функции, изискващи смяната на координатната система.
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 | !PRINT SYMB_POS_X, SYMB_POS_Y, SYMB_POS_Z !RULED{2} 3, 1+2+4 +16+32, ! !0, 0, 1, !1, 0, 1, !0, 1, 1, ! !0, 0, 5, !1, 0, 5, !1, 1, 5 !!!---Create LINEAR Geometry---!!! !!!---Linii---!!! IF linearElements = "ëèíèè" AND surfaceElements <> "coon" THEN PEN colorLines FOR i=1 TO u FOR j=1 TO v IF NOT(hideDiagonals) THEN LIN_ px[i][j], py[i][j], pz[i][j], px[i+1][j+1], py[i+1][j+1], pz[i+1][j+1] ENDIF LIN_ px[i][j], py[i][j], pz[i][j], px[i+1][j], py[i+1][j], pz[i+1][j] LIN_ px[i][j], py[i][j], pz[i][j], px[i][j+1], py[i][j+1], pz[i][j+1] IF i = u THEN LIN_ px[i+1][j], py[i+1][j], pz[i+1][j], px[i+1][j+1], py[i+1][j+1], pz[i+1][j+1] ENDIF IF j = v THEN LIN_ px[i][j+1], py[i][j+1], pz[i][j+1], px[i+1][j+1], py[i+1][j+1], pz[i+1][j+1] ENDIF NEXT j NEXT i ENDIF IF linearElements = "ëèíèè" AND surfaceElements = "coon" THEN PEN colorLines FOR i=1 TO u LIN_ px[i][1], py[i][1], pz[i][1], px[i+1][1], py[i+1][1], pz[i+1][1] LIN_ px[i][v+1], py[i][v+1], pz[i][v+1], px[i+1][v+1], py[i+1][v+1], pz[i+1][v+1] NEXT i FOR i=1 TO v LIN_ px[1][i], py[1][i], pz[1][i], px[1][i+1], py[1][i+1], pz[1][i+1] LIN_ px[u+1][i], py[u+1][i], pz[u+1][i], px[u+1][i+1], py[u+1][i+1], pz[u+1][i+1] NEXT i ENDIF !!!---END Linii---!!! !!!---Mnogosteni---!!! MATERIAL matLinears PEN linii IF linearElements = "ìíîãîñòåíè" AND rr > 0 AND surfaceElements = "coon" THEN RESOL sides FOR i=1 TO u kk[1] = px[i+1][1] - px[i][1] kk[2] = py[i+1][1] - py[i][1] kk[3] = pz[i+1][1] - pz[i][1] IF kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3] > 0 THEN IF ABS(kk[1]) < 0.0001 AND ABS(kk[2]) < 0.0001 THEN ii[1] = 1 ii[2] = 0 ii[3] = 0 ELSE ii[1] = -kk[2] ii[2] = kk[1] ii[3] = 0 length = SQR(ii[1]*ii[1] + ii[2]*ii[2]) ii[1] = ii[1]/length ii[2] = ii[2]/length ENDIF length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3]) kk[1] = kk[1]/length kk[2] = kk[2]/length kk[3] = kk[3]/length jj[1] = ii[2]*kk[3] - ii[3]*kk[2] jj[2] = ii[3]*kk[1] - ii[1]*kk[3] jj[3] = ii[1]*kk[2] - ii[2]*kk[1] XFORM ii[1], jj[1], kk[1], px[i][1], ii[2], jj[2], kk[2], py[i][1], ii[3], jj[3], kk[3], pz[i][1] CYLIND length, rr DEL 1 ENDIF kk[1] = px[i+1][v+1] - px[i][v+1] kk[2] = py[i+1][v+1] - py[i][v+1] kk[3] = pz[i+1][v+1] - pz[i][v+1] IF kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3] > 0 THEN IF ABS(kk[1]) < 0.0001 AND ABS(kk[2]) < 0.0001 THEN ii[1] = 1 ii[2] = 0 ii[3] = 0 ELSE ii[1] = -kk[2] ii[2] = kk[1] ii[3] = 0 length = SQR(ii[1]*ii[1] + ii[2]*ii[2]) ii[1] = ii[1]/length ii[2] = ii[2]/length ENDIF length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3]) kk[1] = kk[1]/length kk[2] = kk[2]/length kk[3] = kk[3]/length jj[1] = ii[2]*kk[3] - ii[3]*kk[2] jj[2] = ii[3]*kk[1] - ii[1]*kk[3] jj[3] = ii[1]*kk[2] - ii[2]*kk[1] XFORM ii[1], jj[1], kk[1], px[i][v+1], ii[2], jj[2], kk[2], py[i][v+1], ii[3], jj[3], kk[3], pz[i][v+1] CYLIND length, rr DEL 1 ENDIF NEXT i FOR i=1 TO v kk[1] = px[1][i] - px[1][i+1] kk[2] = py[1][i] - py[1][i+1] kk[3] = pz[1][i] - pz[1][i+1] IF kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3] > 0 THEN IF ABS(kk[1]) < 0.0001 AND ABS(kk[2]) < 0.0001 THEN ii[1] = 1 ii[2] = 0 ii[3] = 0 ELSE ii[1] = -kk[2] ii[2] = kk[1] ii[3] = 0 length = SQR(ii[1]*ii[1] + ii[2]*ii[2]) ii[1] = ii[1]/length ii[2] = ii[2]/length ENDIF length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3]) kk[1] = kk[1]/length kk[2] = kk[2]/length kk[3] = kk[3]/length jj[1] = ii[2]*kk[3] - ii[3]*kk[2] jj[2] = ii[3]*kk[1] - ii[1]*kk[3] jj[3] = ii[1]*kk[2] - ii[2]*kk[1] XFORM ii[1], jj[1], kk[1], px[1][i+1], ii[2], jj[2], kk[2], py[1][i+1], ii[3], jj[3], kk[3], pz[1][i+1] CYLIND length, rr DEL 1 ENDIF kk[1] = px[u+1][i] - px[u+1][i+1] kk[2] = py[u+1][i] - py[u+1][i+1] kk[3] = pz[u+1][i] - pz[u+1][i+1] IF kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3] > 0 THEN IF ABS(kk[1]) < 0.0001 AND ABS(kk[2]) < 0.0001 THEN ii[1] = 1 ii[2] = 0 ii[3] = 0 ELSE ii[1] = -kk[2] ii[2] = kk[1] ii[3] = 0 length = SQR(ii[1]*ii[1] + ii[2]*ii[2]) ii[1] = ii[1]/length ii[2] = ii[2]/length ENDIF length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3]) kk[1] = kk[1]/length kk[2] = kk[2]/length kk[3] = kk[3]/length jj[1] = ii[2]*kk[3] - ii[3]*kk[2] jj[2] = ii[3]*kk[1] - ii[1]*kk[3] jj[3] = ii[1]*kk[2] - ii[2]*kk[1] XFORM ii[1], jj[1], kk[1], px[u+1][i+1], ii[2], jj[2], kk[2], py[u+1][i+1], ii[3], jj[3], kk[3], pz[u+1][i+1] CYLIND length, rr DEL 1 ENDIF NEXT i ENDIF !!!--- Not coon ---!!! IF linearElements = "ìíîãîñòåíè" AND rr > 0 AND surfaceElements <> "coon" THEN RESOL sides FOR i=1 TO u FOR j=1 TO v IF NOT(hideDiagonals) THEN kk[1] = px[i+1][j+1] - px[i][j] kk[2] = py[i+1][j+1] - py[i][j] kk[3] = pz[i+1][j+1] - pz[i][j] IF kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3] > 0 THEN IF ABS(kk[1]) < 0.0001 AND ABS(kk[2]) < 0.0001 THEN ii[1] = 1 ii[2] = 0 ii[3] = 0 ELSE ii[1] = -kk[2] ii[2] = kk[1] ii[3] = 0 length = SQR(ii[1]*ii[1] + ii[2]*ii[2]) ii[1] = ii[1]/length ii[2] = ii[2]/length ENDIF length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3]) kk[1] = kk[1]/length kk[2] = kk[2]/length kk[3] = kk[3]/length jj[1] = ii[2]*kk[3] - ii[3]*kk[2] jj[2] = ii[3]*kk[1] - ii[1]*kk[3] jj[3] = ii[1]*kk[2] - ii[2]*kk[1] XFORM ii[1], jj[1], kk[1], px[i][j], ii[2], jj[2], kk[2], py[i][j], ii[3], jj[3], kk[3], pz[i][j] CYLIND length, rr DEL 1 ENDIF ENDIF kk[1] = px[i+1][j] - px[i][j] kk[2] = py[i+1][j] - py[i][j] kk[3] = pz[i+1][j] - pz[i][j] IF kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3] > 0 THEN IF ABS(kk[1]) < 0.0001 AND ABS(kk[2]) < 0.0001 THEN ii[1] = 1 ii[2] = 0 ii[3] = 0 ELSE ii[1] = -kk[2] ii[2] = kk[1] ii[3] = 0 length = SQR(ii[1]*ii[1] + ii[2]*ii[2]) ii[1] = ii[1]/length ii[2] = ii[2]/length ENDIF length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3]) kk[1] = kk[1]/length kk[2] = kk[2]/length kk[3] = kk[3]/length jj[1] = ii[2]*kk[3] - ii[3]*kk[2] jj[2] = ii[3]*kk[1] - ii[1]*kk[3] jj[3] = ii[1]*kk[2] - ii[2]*kk[1] XFORM ii[1], jj[1], kk[1], px[i][j], ii[2], jj[2], kk[2], py[i][j], ii[3], jj[3], kk[3], pz[i][j] CYLIND length, rr DEL 1 ENDIF kk[1] = px[i][j+1] - px[i][j] kk[2] = py[i][j+1] - py[i][j] kk[3] = pz[i][j+1] - pz[i][j] IF kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3] > 0 THEN IF ABS(kk[1]) < 0.0001 AND ABS(kk[2]) < 0.0001 THEN ii[1] = 1 ii[2] = 0 ii[3] = 0 ELSE ii[1] = -kk[2] ii[2] = kk[1] ii[3] = 0 length = SQR(ii[1]*ii[1] + ii[2]*ii[2]) ii[1] = ii[1]/length ii[2] = ii[2]/length ENDIF length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3]) kk[1] = kk[1]/length kk[2] = kk[2]/length kk[3] = kk[3]/length jj[1] = ii[2]*kk[3] - ii[3]*kk[2] jj[2] = ii[3]*kk[1] - ii[1]*kk[3] jj[3] = ii[1]*kk[2] - ii[2]*kk[1] XFORM ii[1], jj[1], kk[1], px[i][j], ii[2], jj[2], kk[2], py[i][j], ii[3], jj[3], kk[3], pz[i][j] CYLIND length, rr DEL 1 ENDIF IF i = u THEN kk[1] = px[i+1][j+1] - px[i+1][j] kk[2] = py[i+1][j+1] - py[i+1][j] kk[3] = pz[i+1][j+1] - pz[i+1][j] IF kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3] > 0 THEN IF ABS(kk[1]) < 0.0001 AND ABS(kk[2]) < 0.0001 THEN ii[1] = 1 ii[2] = 0 ii[3] = 0 ELSE ii[1] = -kk[2] ii[2] = kk[1] ii[3] = 0 length = SQR(ii[1]*ii[1] + ii[2]*ii[2]) ii[1] = ii[1]/length ii[2] = ii[2]/length ENDIF length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3]) kk[1] = kk[1]/length kk[2] = kk[2]/length kk[3] = kk[3]/length jj[1] = ii[2]*kk[3] - ii[3]*kk[2] jj[2] = ii[3]*kk[1] - ii[1]*kk[3] jj[3] = ii[1]*kk[2] - ii[2]*kk[1] XFORM ii[1], jj[1], kk[1], px[i+1][j], ii[2], jj[2], kk[2], py[i+1][j], ii[3], jj[3], kk[3], pz[i+1][j] CYLIND length, rr DEL 1 ENDIF ENDIF IF j = v THEN kk[1] = px[i+1][j+1] - px[i][j+1] kk[2] = py[i+1][j+1] - py[i][j+1] kk[3] = pz[i+1][j+1] - pz[i][j+1] IF kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3] > 0 THEN IF ABS(kk[1]) < 0.0001 AND ABS(kk[2]) < 0.0001 THEN ii[1] = 1 ii[2] = 0 ii[3] = 0 ELSE ii[1] = -kk[2] ii[2] = kk[1] ii[3] = 0 length = SQR(ii[1]*ii[1] + ii[2]*ii[2]) ii[1] = ii[1]/length ii[2] = ii[2]/length ENDIF length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3]) kk[1] = kk[1]/length kk[2] = kk[2]/length kk[3] = kk[3]/length jj[1] = ii[2]*kk[3] - ii[3]*kk[2] jj[2] = ii[3]*kk[1] - ii[1]*kk[3] jj[3] = ii[1]*kk[2] - ii[2]*kk[1] XFORM ii[1], jj[1], kk[1], px[i][j+1], ii[2], jj[2], kk[2], py[i][j+1], ii[3], jj[3], kk[3], pz[i][j+1] CYLIND length, rr DEL 1 ENDIF ENDIF NEXT j NEXT i ENDIF !!!---END Mnogosteni---!!! !!!---END Create LINEAR Geometry---!!! !!!---SURFACE Geometry---!!! MATERIAL matSurface !PEN linii PEN liniiSurface !!!---Create FACE Geometry---!!! IF surfaceElements = "ëèöà" THEN FOR i=1 TO u FOR j=1 TO v ii[1] = px[i+1][j+1] - px[i][j] ii[2] = py[i+1][j+1] - py[i][j] ii[3] = pz[i+1][j+1] - pz[i][j] jj[1] = px[i+1][j] - px[i][j] jj[2] = py[i+1][j] - py[i][j] jj[3] = pz[i+1][j] - pz[i][j] kk[1] = ii[2]*jj[3] - ii[3]*jj[2] kk[2] = ii[3]*jj[1] - ii[1]*jj[3] kk[3] = ii[1]*jj[2] - ii[2]*jj[1] length = kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3] IF (length > 0.0001) THEN PLANE_ 3, px[i][j], py[i][j], pz[i][j], 1, px[i+1][j+1], py[i+1][j+1], pz[i+1][j+1], 1, px[i+1][j], py[i+1][j], pz[i+1][j], 1 ENDIF ii[1] = px[i+1][j+1] - px[i][j] ii[2] = py[i+1][j+1] - py[i][j] ii[3] = pz[i+1][j+1] - pz[i][j] jj[1] = px[i][j+1] - px[i][j] jj[2] = py[i][j+1] - py[i][j] jj[3] = pz[i][j+1] - pz[i][j] kk[1] = ii[2]*jj[3] - ii[3]*jj[2] kk[2] = ii[3]*jj[1] - ii[1]*jj[3] kk[3] = ii[1]*jj[2] - ii[2]*jj[1] length = kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3] IF (length > 0.0001) THEN PLANE_ 3, px[i][j], py[i][j], pz[i][j], 1, px[i+1][j+1], py[i+1][j+1], pz[i+1][j+1], 1, px[i][j+1], py[i][j+1], pz[i][j+1], 1 ENDIF NEXT j NEXT i ENDIF !!!---END Create FACE Geometry---!!! !!!---Create COONS Geometry---!!! IF surfaceElements = "coons" THEN FOR i=1 TO u FOR j=1 TO v COONS 2, 2, 0, px[i][j], py[i][j], pz[i][j] ,px[i][j+1], py[i][j+1], pz[i][j+1], px[i+1][j], py[i+1][j], pz[i+1][j] ,px[i+1][j+1], py[i+1][j+1], pz[i+1][j+1], px[i][j], py[i][j], pz[i][j] ,px[i+1][j], py[i+1][j], pz[i+1][j], px[i][j+1], py[i][j+1], pz[i][j+1] ,px[i+1][j+1], py[i+1][j+1], pz[i+1][j+1] NEXT j NEXT i ENDIF !!!---END Create COONS Geometry---!!! !!!---Create COON Geometry---!!! IF surfaceElements = "coon" THEN FOR i=1 to u+1 PUT px[i][1] PUT py[i][1] PUT pz[i][1] NEXT i FOR i=1 to u+1 PUT px[i][v+1] PUT py[i][v+1] PUT pz[i][v+1] NEXT i FOR i=1 to v+1 PUT px[1][i] PUT py[1][i] PUT pz[1][i] NEXT i FOR i=1 to v+1 PUT px[u+1][i] PUT py[u+1][i] PUT pz[u+1][i] NEXT i COONS u+1, v+1, 0, GET(6*(u+v+2)) ENDIF !!!---END Create COON Geometry---!!! !!!---Create PRISM Geometry---!!! IF surfaceElements = "ïðèçìè" and thickness > 0 THEN FOR i=1 TO u FOR j=1 TO v ii[1] = px[i+1][j+1] - px[i][j] ii[2] = py[i+1][j+1] - py[i][j] ii[3] = pz[i+1][j+1] - pz[i][j] jj[1] = px[i+1][j] - px[i][j] jj[2] = py[i+1][j] - py[i][j] jj[3] = pz[i+1][j] - pz[i][j] kk[1] = ii[2]*jj[3] - ii[3]*jj[2] kk[2] = ii[3]*jj[1] - ii[1]*jj[3] kk[3] = ii[1]*jj[2] - ii[2]*jj[1] length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3]) IF (length > 0.0001) THEN XFORM ii[1], jj[1], kk[1]/length, px[i][j] - (kk[1]/length)*(thickness/2), ii[2], jj[2], kk[2]/length, py[i][j] - (kk[2]/length)*(thickness/2), ii[3], jj[3], kk[3]/length, pz[i][j] - (kk[3]/length)*(thickness/2) PRISM 3, thickness, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0 DEL 1 ENDIF ii[1] = px[i][j+1] - px[i][j] ii[2] = py[i][j+1] - py[i][j] ii[3] = pz[i][j+1] - pz[i][j] jj[1] = px[i+1][j+1] - px[i][j] jj[2] = py[i+1][j+1] - py[i][j] jj[3] = pz[i+1][j+1] - pz[i][j] kk[1] = ii[2]*jj[3] - ii[3]*jj[2] kk[2] = ii[3]*jj[1] - ii[1]*jj[3] kk[3] = ii[1]*jj[2] - ii[2]*jj[1] length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3]) IF (length > 0.0001) THEN XFORM ii[1], jj[1], kk[1]/length, px[i][j] - (kk[1]/length)*(thickness/2), ii[2], jj[2], kk[2]/length, py[i][j] - (kk[2]/length)*(thickness/2), ii[3], jj[3], kk[3]/length, pz[i][j] - (kk[3]/length)*(thickness/2) PRISM 3, thickness, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0 DEL 1 ENDIF NEXT j NEXT i ENDIF !!!---END Create PRISM Geometry---!!! !!!---END SURFACE Geometry---!!! !!!---Hotspots And Controls Geometry---!!! IF HotspotsX OR HotspotsY OR HotspotsZ THEN FOR i=1 TO 4 FOR j=1 TO 4 IF mode = "êóáè÷åí" OR (mode = "ëèíååí" AND (i=1 OR i=4) AND (j=1 OR j=4)) THEN IF HotspotsX THEN hotspot bx[i][j], by[i][j], bz[i][j], UNid, bx[i][j], 2 : UNid=UNid+1 hotspot 0, by[i][j], bz[i][j], UNid, bx[i][j], 1 + 128 : UNid=UNid+1 hotspot -1, by[i][j], bz[i][j], UNid, bx[i][j], 3 : UNid=UNid+1 ENDIF IF HotspotsY THEN hotspot bx[i][j], by[i][j], bz[i][j], UNid, by[i][j], 2 : UNid=UNid+1 hotspot bx[i][j], 0, bz[i][j], UNid, by[i][j], 1 + 128 : UNid=UNid+1 hotspot bx[i][j], -1, bz[i][j], UNid, by[i][j], 3 : UNid=UNid+1 ENDIF IF HotspotsZ THEN hotspot bx[i][j], by[i][j], bz[i][j], UNid, bz[i][j], 2 : UNid=UNid+1 hotspot bx[i][j], by[i][j], 0, UNid, bz[i][j], 1 + 128 : UNid=UNid+1 hotspot bx[i][j], by[i][j], -1, UNid, bz[i][j], 3 : UNid=UNid+1 ENDIF IF VertexCubesSide > 0 THEN MATERIAL matPoints ADD bx[i][j] - VertexCubesSide/2, by[i][j] - VertexCubesSide/2, bz[i][j] - VertexCubesSide/2 BLOCK VertexCubesSide, VertexCubesSide, VertexCubesSide DEL 1 ENDIF ENDIF IF lines AND mode = "êóáè÷åí" THEN PEN penLinii tx = 1 ty = 1 LIN_ bx[tx][ty], by[tx][ty], bz[tx][ty], bx[tx][ty + 1], by[tx][ty + 1], bz[tx][ty + 1] LIN_ bx[tx][ty], by[tx][ty], bz[tx][ty], bx[tx + 1][ty], by[tx + 1][ty], bz[tx + 1][ty] LIN_ bx[tx + 1][ty + 1], by[tx + 1][ty + 1], bz[tx + 1][ty + 1], bx[tx + 1][ty], by[tx + 1][ty], bz[tx + 1][ty] LIN_ bx[tx + 1][ty + 1], by[tx + 1][ty + 1], bz[tx + 1][ty + 1], bx[tx][ty + 1], by[tx][ty + 1], bz[tx][ty + 1] tx = 1 ty = 3 LIN_ bx[tx][ty], by[tx][ty], bz[tx][ty], bx[tx][ty + 1], by[tx][ty + 1], bz[tx][ty + 1] LIN_ bx[tx][ty], by[tx][ty], bz[tx][ty], bx[tx + 1][ty], by[tx + 1][ty], bz[tx + 1][ty] LIN_ bx[tx + 1][ty + 1], by[tx + 1][ty + 1], bz[tx + 1][ty + 1], bx[tx + 1][ty], by[tx + 1][ty], bz[tx + 1][ty] LIN_ bx[tx + 1][ty + 1], by[tx + 1][ty + 1], bz[tx + 1][ty + 1], bx[tx][ty + 1], by[tx][ty + 1], bz[tx][ty + 1] tx = 3 ty = 1 LIN_ bx[tx][ty], by[tx][ty], bz[tx][ty], bx[tx][ty + 1], by[tx][ty + 1], bz[tx][ty + 1] LIN_ bx[tx][ty], by[tx][ty], bz[tx][ty], bx[tx + 1][ty], by[tx + 1][ty], bz[tx + 1][ty] LIN_ bx[tx + 1][ty + 1], by[tx + 1][ty + 1], bz[tx + 1][ty + 1], bx[tx + 1][ty], by[tx + 1][ty], bz[tx + 1][ty] LIN_ bx[tx + 1][ty + 1], by[tx + 1][ty + 1], bz[tx + 1][ty + 1], bx[tx][ty + 1], by[tx][ty + 1], bz[tx][ty + 1] tx = 3 ty = 3 LIN_ bx[tx][ty], by[tx][ty], bz[tx][ty], bx[tx][ty + 1], by[tx][ty + 1], bz[tx][ty + 1] LIN_ bx[tx][ty], by[tx][ty], bz[tx][ty], bx[tx + 1][ty], by[tx + 1][ty], bz[tx + 1][ty] LIN_ bx[tx + 1][ty + 1], by[tx + 1][ty + 1], bz[tx + 1][ty + 1], bx[tx + 1][ty], by[tx + 1][ty], bz[tx + 1][ty] LIN_ bx[tx + 1][ty + 1], by[tx + 1][ty + 1], bz[tx + 1][ty + 1], bx[tx][ty + 1], by[tx][ty + 1], bz[tx][ty + 1] ENDIF NEXT j NEXT i ENDIF !!!---END Hotspots---!!! |
Следващият скрипт е Parameter script, където се задават възможните стойности на някои параметрите с изборна стойност, както и там се дефинира кореспонденцията между UI бутоните за хомотетия и пресмятането на новите стойности на координатите след хомотетията.
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | VALUES "mode", "êóáè÷åí", "ëèíååí" VALUES "surfaceElements", "ïðèçìè", "ëèöà", "áåç", "coons", "coon" VALUES "linearElements", "áåç", "ìíîãîñòåíè", "ëèíèè" IF linearElements <> "ìíîãîñòåíè" THEN LOCK "rr", "sides" ENDIF IF linearElements <> "ëèíèè" THEN LOCK "colorLines" ENDIF IF surfaceElements = "áåç" OR surfaceElements = "ëèöà" THEN LOCK "thickness" ENDIF IF lines = 0 THEN LOCK "penLinii" ENDIF !!!---Transformation Group---!!! IF GLOB_UI_BUTTON_ID = 1 THEN temp = 0 FOR i=1 TO 4 FOR j=1 TO 4 temp = temp + bx[i][j] NEXT j NEXT i temp = temp/16 FOR i=1 TO 4 FOR j=1 TO 4 PARAMETERS bx[i][j] = homX * bx[i][j] + (1 - homX)*temp NEXT j NEXT i PARAMETERS homX = 1 ENDIF IF GLOB_UI_BUTTON_ID = 2 THEN temp = 0 FOR i=1 TO 4 FOR j=1 TO 4 temp = temp + by[i][j] NEXT j NEXT i temp = temp/16 FOR i=1 TO 4 FOR j=1 TO 4 PARAMETERS by[i][j] = homY * by[i][j] + (1- homY)*temp NEXT j NEXT i PARAMETERS homY = 1 ENDIF IF GLOB_UI_BUTTON_ID = 3 THEN temp = 0 FOR i=1 TO 4 FOR j=1 TO 4 temp = temp + bz[i][j] NEXT j NEXT i temp = temp/16 FOR i=1 TO 4 FOR j=1 TO 4 PARAMETERS bz[i][j] = homZ * bz[i][j] + (1- homZ)*temp NEXT j NEXT i PARAMETERS homZ = 1 ENDIF !!!---END Transformation Group---!!! |
И последният скрипт е Interface script, където се дефинира прозореца от UI частта с бутоните и fieldset-ове.
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | UI_DIALOG "Ïîâúðõíèíà íà Áåçèå - Äåÿí Éîñèôîâ - 11.01.2013 ãîäèíà" !!!---Size is 444 x 266---!!! !!!---Transformation Group---!!! x = 10 y = 10 UI_GROUPBOX "Òðàíñôîðìàöèè", x, y, 202, 125 UI_BUTTON UI_FUNCTION, "Õîìîòåòèÿ ïî X", x+10, y+20, 100,20, 1 UI_INFIELD{3} homX, x+120, y+20, 60, 20 !UI_OUTFIELD "> 0", x+135+33, y+20+3, 30, 20 UI_BUTTON UI_FUNCTION, "Õîìîòåòèÿ ïî Y", x+10, y+45, 100,20, 2 UI_INFIELD{3} homY, x+120, y+45, 60, 20 !UI_OUTFIELD "> 0", x+135+33, y+45+3, 30, 20 UI_BUTTON UI_FUNCTION, "Õîìîòåòèÿ ïî Z", x+10, y+70, 100,20, 3 UI_INFIELD{3} homZ, x+120, y+70, 60, 20 !UI_OUTFIELD "> 0", x+135+33, y+70+3, 30, 20 UI_OUTFIELD "Õîìîòåòèèòå ñà ñïðÿìî öåíòúðà íà", x+10, y+95+0, 180, 12 UI_OUTFIELD "òåæåñòòà íà êîíòðîëíèòå òî÷êè.", x+10, y+95+13, 180, 12 !!!---END Transformation Group---!!! !!!---Hotspots Group---!!! x = 202 + 2*10 y = 10 UI_GROUPBOX "Hotspots", x, y, 202, 125 UI_INFIELD{3} HotspotsX, x+10, y+20, 15, 15 UI_OUTFIELD "3D Hotspots X", x+28, y+20, 80, 15 UI_INFIELD{3} HotspotsY, x+10, y+38, 15, 15 UI_OUTFIELD "3D Hotspots Y", x+28, y+38, 80, 15 UI_INFIELD{3} HotspotsZ, x+10, y+56, 15, 15 UI_OUTFIELD "3D Hotspots Z", x+28, y+56, 80, 15 UI_INFIELD{3} Hotspots2D, x+10, y+74, 15, 15 UI_OUTFIELD "2D Hotspots", x+28, y+74, 80, 15 !!!---END Hotspots Group---!!! !!!---3D Details Group---!!! x = 10 y = 140 UI_GROUPBOX "3Ä Äåòàéëè", x, y, 202, 120 UI_INFIELD{3} surfaceElements, x+10, y+20, 15+50, 15 UI_OUTFIELD "Ïîâúðõíèííè åëåìåíòè", x+28+50, y+20, 120, 15 UI_INFIELD{3} linearElements, x+10, y+40, 15+50, 15 UI_OUTFIELD "Ëèíåéíè åëåìåíòè", x+28+50, y+40, 120, 15 UI_INFIELD{3} VertexCubesSide, x+10, y+60, 15+50-20, 15 UI_OUTFIELD "Ñòðàíà êîíòðîëíè êóá÷åòà", x+28+50-20, y+60, 120+20, 15 UI_INFIELD{3} lines, x+10+15, y+80, 15+50-20-15, 15 UI_OUTFIELD "Êîíòðîëíè ëèíèè", x+28+50-20, y+80, 120+20, 15 UI_INFIELD{3} mode, x+10, y+100, 15+50, 15 UI_OUTFIELD "Ðåæèì íà êðèâàòà", x+28+50, y+100, 120, 15 !!!---END 3D Details Group---!!! !!!---Hide Group---!!! x = 202 + 2*10 y = 140 UI_GROUPBOX "Ñêðèé", x, y, 202, 120 UI_INFIELD{3} hideDiagonals, x+10, y+20, 15, 15 UI_OUTFIELD "Ñêðèé äèàãîíàëè", x+28, y+20, 160, 15 UI_INFIELD{3} hideConture, x+10, y+38, 15, 15 UI_OUTFIELD "Ñêðèé êîíòóð", x+28, y+38, 160, 15 UI_INFIELD{3} hideULines, x+10, y+56, 15, 15 UI_OUTFIELD "Ñêðèé U ëèíèè", x+28, y+56, 160, 15 UI_INFIELD{3} hideVLines, x+10, y+74, 15, 15 UI_OUTFIELD "Ñêðèé V ëèíèè", x+28, y+74, 160, 15 !!!---END Hide Group---!!! |
В общи линии това е по кода. Надявам се този GDL обект, да стане полезен и за други хора освен за самия мен 🙂 и да съм успял да събудя интерес у някой да се пробва да напише нещо и на този (макар и доста странен и окастрен) скриптов език. 🙂
P.S.: Забелязах, че при copy-paste от Архикад нещата, които са на кирилица в скрипта стават на каракацили и джуджуфлечки. 🙂 За съжаление в момента нямам време да ги преписвам 1 по 1, затова може да отворите скриптовете в Архикад, за по-подробен преглед. Когато намеря време ще оправя този проблем. 🙂